Commit c526d13e453cc8acd07dcca96598abc06ae6e4d6

Authored by Volodymyr Babak
2 parents 4686b232 3f9f6efc

Merge remote-tracking branch 'upstream/develop/2.5.5' into feature/edge

Showing 100 changed files with 1302 additions and 450 deletions

Too many changes to show.

To preserve performance only 100 of 130 files are displayed.

... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>application</artifactId>
... ...
... ... @@ -64,6 +64,7 @@ BEGIN
64 64 AND tablename like 'ts_kv_' || '%'
65 65 AND tablename != 'ts_kv_latest'
66 66 AND tablename != 'ts_kv_dictionary'
  67 + AND tablename != 'ts_kv_indefinite'
67 68 LOOP
68 69 IF partition != partition_by_max_ttl_date THEN
69 70 IF partition_year IS NOT NULL THEN
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
28 28 import org.thingsboard.server.actors.ActorSystemContext;
29 29 import org.thingsboard.server.actors.TbActorCtx;
30 30 import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
  31 +import org.thingsboard.server.common.data.DataConstants;
31 32 import org.thingsboard.server.common.data.Device;
32 33 import org.thingsboard.server.common.data.id.DeviceId;
33 34 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -79,8 +80,6 @@ import java.util.UUID;
79 80 import java.util.function.Consumer;
80 81 import java.util.stream.Collectors;
81 82
82   -import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
83   -import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
84 83
85 84 /**
86 85 * @author Andrew Shvayka
... ... @@ -279,17 +278,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
279 278 ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture;
280 279 ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture;
281 280 if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
282   - clientAttributesFuture = findAllAttributesByScope(CLIENT_SCOPE);
283   - sharedAttributesFuture = findAllAttributesByScope(SHARED_SCOPE);
  281 + clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE);
  282 + sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE);
284 283 } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
285   - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
286   - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
  284 + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
  285 + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
287 286 } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
288 287 clientAttributesFuture = Futures.immediateFuture(Collections.emptyList());
289   - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
  288 + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
290 289 } else {
291 290 sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList());
292   - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
  291 + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
293 292 }
294 293 return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture));
295 294 }
... ... @@ -316,7 +315,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
316 315 AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
317 316 if (msg.isDeleted()) {
318 317 List<String> sharedKeys = msg.getDeletedKeys().stream()
319   - .filter(key -> SHARED_SCOPE.equals(key.getScope()))
  318 + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
320 319 .map(AttributeKey::getAttributeKey)
321 320 .collect(Collectors.toList());
322 321 if (!sharedKeys.isEmpty()) {
... ... @@ -324,7 +323,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
324 323 hasNotificationData = true;
325 324 }
326 325 } else {
327   - if (SHARED_SCOPE.equals(msg.getScope())) {
  326 + if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
328 327 List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
329 328 if (attributes.size() > 0) {
330 329 List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
... ... @@ -334,7 +333,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
334 333 hasNotificationData = true;
335 334 }
336 335 } else {
337   - log.debug("[{}] No public server side attributes changed!", deviceId);
  336 + log.debug("[{}] No public shared side attributes changed!", deviceId);
338 337 }
339 338 }
340 339 }
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 19 import org.springframework.beans.factory.annotation.Autowired;
19 20 import org.springframework.security.access.prepost.PreAuthorize;
20 21 import org.springframework.web.bind.annotation.PathVariable;
... ... @@ -59,7 +60,11 @@ public class AdminController extends BaseController {
59 60 public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException {
60 61 try {
61 62 accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
62   - return checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key));
  63 + AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key));
  64 + if (adminSettings.getKey().equals("mail")) {
  65 + ((ObjectNode) adminSettings.getJsonValue()).put("password", "");
  66 + }
  67 + return adminSettings;
63 68 } catch (Exception e) {
64 69 throw handleException(e);
65 70 }
... ... @@ -74,6 +79,7 @@ public class AdminController extends BaseController {
74 79 adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings));
75 80 if (adminSettings.getKey().equals("mail")) {
76 81 mailService.updateMailConfiguration();
  82 + ((ObjectNode) adminSettings.getJsonValue()).put("password", "");
77 83 }
78 84 return adminSettings;
79 85 } catch (Exception e) {
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.core.JsonProcessingException;
18 19 import com.fasterxml.jackson.databind.ObjectMapper;
19 20 import com.fasterxml.jackson.databind.node.ArrayNode;
20 21 import com.fasterxml.jackson.databind.node.ObjectNode;
... ... @@ -624,6 +625,12 @@ public abstract class BaseController {
624 625 case ALARM_CLEAR:
625 626 msgType = DataConstants.ALARM_CLEAR;
626 627 break;
  628 + case ASSIGNED_FROM_TENANT:
  629 + msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT;
  630 + break;
  631 + case ASSIGNED_TO_TENANT:
  632 + msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
  633 + break;
627 634 case ASSIGNED_TO_EDGE:
628 635 msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
629 636 break;
... ... @@ -649,7 +656,17 @@ public abstract class BaseController {
649 656 String strCustomerName = extractParameter(String.class, 2, additionalInfo);
650 657 metaData.putValue("unassignedCustomerId", strCustomerId);
651 658 metaData.putValue("unassignedCustomerName", strCustomerName);
652   - } if (actionType == ActionType.ASSIGNED_TO_EDGE) {
  659 + } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) {
  660 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  661 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  662 + metaData.putValue("assignedFromTenantId", strTenantId);
  663 + metaData.putValue("assignedFromTenantName", strTenantName);
  664 + } else if (actionType == ActionType.ASSIGNED_TO_TENANT) {
  665 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  666 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  667 + metaData.putValue("assignedToTenantId", strTenantId);
  668 + metaData.putValue("assignedToTenantName", strTenantName);
  669 + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) {
653 670 String strEdgeId = extractParameter(String.class, 1, additionalInfo);
654 671 metaData.putValue("assignedEdgeId", strEdgeId);
655 672 } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {
... ... @@ -718,6 +735,15 @@ public abstract class BaseController {
718 735 return result;
719 736 }
720 737
  738 + protected <E extends HasName> String entityToStr(E entity) {
  739 + try {
  740 + return json.writeValueAsString(json.valueToTree(entity));
  741 + } catch (JsonProcessingException e) {
  742 + log.warn("[{}] Failed to convert entity to string!", entity, e);
  743 + }
  744 + return null;
  745 + }
  746 +
721 747 protected void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, CustomerId customerId, ActionType edgeEventAction) {
722 748 try {
723 749 sendNotificationMsgToEdgeService(tenantId, edgeId, null, json.writeValueAsString(customerId), EdgeEventType.EDGE, edgeEventAction);
... ...
... ... @@ -39,10 +39,11 @@ import org.thingsboard.server.common.data.DataConstants;
39 39 import org.thingsboard.server.common.data.Device;
40 40 import org.thingsboard.server.common.data.EntitySubtype;
41 41 import org.thingsboard.server.common.data.EntityType;
  42 +import org.thingsboard.server.common.data.Tenant;
42 43 import org.thingsboard.server.common.data.audit.ActionType;
43 44 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
44 45 import org.thingsboard.server.common.data.edge.Edge;
45   -import org.thingsboard.server.common.data.edge.EdgeEventType;
  46 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
46 47 import org.thingsboard.server.common.data.exception.ThingsboardException;
47 48 import org.thingsboard.server.common.data.id.CustomerId;
48 49 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -52,13 +53,14 @@ import org.thingsboard.server.common.data.page.TextPageData;
52 53 import org.thingsboard.server.common.data.page.TextPageLink;
53 54 import org.thingsboard.server.common.data.page.TimePageData;
54 55 import org.thingsboard.server.common.data.page.TimePageLink;
55   -import org.thingsboard.server.common.data.rule.RuleChain;
56 56 import org.thingsboard.server.common.data.security.DeviceCredentials;
  57 +import org.thingsboard.server.common.msg.TbMsg;
  58 +import org.thingsboard.server.common.msg.TbMsgDataType;
  59 +import org.thingsboard.server.common.msg.TbMsgMetaData;
57 60 import org.thingsboard.server.dao.device.claim.ClaimResponse;
58 61 import org.thingsboard.server.dao.device.claim.ClaimResult;
59 62 import org.thingsboard.server.dao.exception.IncorrectParameterException;
60 63 import org.thingsboard.server.dao.model.ModelConstants;
61   -import org.thingsboard.server.gen.transport.TransportProtos;
62 64 import org.thingsboard.server.queue.util.TbCoreComponent;
63 65 import org.thingsboard.server.service.security.model.SecurityUser;
64 66 import org.thingsboard.server.service.security.permission.Operation;
... ... @@ -79,6 +81,7 @@ public class DeviceController extends BaseController {
79 81
80 82 private static final String DEVICE_ID = "deviceId";
81 83 private static final String DEVICE_NAME = "deviceName";
  84 + private static final String TENANT_ID = "tenantId";
82 85
83 86 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
84 87 @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET)
... ... @@ -506,6 +509,56 @@ public class DeviceController extends BaseController {
506 509 }
507 510
508 511 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  512 + @RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST)
  513 + @ResponseBody
  514 + public Device assignDeviceToTenant(@PathVariable(TENANT_ID) String strTenantId,
  515 + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
  516 + checkParameter(TENANT_ID, strTenantId);
  517 + checkParameter(DEVICE_ID, strDeviceId);
  518 + try {
  519 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  520 + Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT);
  521 +
  522 + TenantId newTenantId = new TenantId(toUUID(strTenantId));
  523 + Tenant newTenant = tenantService.findTenantById(newTenantId);
  524 + if (newTenant == null) {
  525 + throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  526 + }
  527 +
  528 + Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device);
  529 +
  530 + logEntityAction(getCurrentUser(), deviceId, assignedDevice,
  531 + assignedDevice.getCustomerId(),
  532 + ActionType.ASSIGNED_TO_TENANT, null, strTenantId, newTenant.getName());
  533 +
  534 + Tenant currentTenant = tenantService.findTenantById(getTenantId());
  535 + pushAssignedFromNotification(currentTenant, newTenantId, assignedDevice);
  536 +
  537 + return assignedDevice;
  538 + } catch (Exception e) {
  539 + logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null,
  540 + null,
  541 + ActionType.ASSIGNED_TO_TENANT, e, strTenantId);
  542 + throw handleException(e);
  543 + }
  544 + }
  545 +
  546 + private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) {
  547 + String data = entityToStr(assignedDevice);
  548 + if (data != null) {
  549 + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data);
  550 + tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null);
  551 + }
  552 + }
  553 +
  554 + private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) {
  555 + TbMsgMetaData metaData = new TbMsgMetaData();
  556 + metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString());
  557 + metaData.putValue("assignedFromTenantName", tenant.getName());
  558 + return metaData;
  559 + }
  560 +
  561 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
509 562 @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
510 563 @ResponseBody
511 564 public Device assignDeviceToEdge(@PathVariable(EDGE_ID) String strEdgeId,
... ...
... ... @@ -197,19 +197,21 @@ public class TelemetryController extends BaseController {
197 197 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
198 198 @ResponseBody
199 199 public DeferredResult<ResponseEntity> getTimeseries(
200   - @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
  200 + @PathVariable("entityType") String entityType,
  201 + @PathVariable("entityId") String entityIdStr,
201 202 @RequestParam(name = "keys") String keys,
202 203 @RequestParam(name = "startTs") Long startTs,
203 204 @RequestParam(name = "endTs") Long endTs,
204 205 @RequestParam(name = "interval", defaultValue = "0") Long interval,
205 206 @RequestParam(name = "limit", defaultValue = "100") Integer limit,
206 207 @RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
  208 + @RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy,
207 209 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
208 210 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
209 211 (result, tenantId, entityId) -> {
210 212 // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
211 213 Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
212   - List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
  214 + List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
213 215 .collect(Collectors.toList());
214 216
215 217 Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
... ...
... ... @@ -146,6 +146,11 @@ public class ThingsboardInstallService {
146 146 databaseTsUpgradeService.upgradeDatabase("2.5.0");
147 147 }
148 148
  149 + case "2.5.4":
  150 + log.info("Upgrading ThingsBoard from version 2.5.4 to 2.5.5 ...");
  151 + if (databaseTsUpgradeService != null) {
  152 + databaseTsUpgradeService.upgradeDatabase("2.5.4");
  153 + }
149 154
150 155 log.info("Updating system data...");
151 156
... ...
... ... @@ -49,6 +49,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
49 49 log.info("Schema updated.");
50 50 break;
51 51 case "2.5.0":
  52 + case "2.5.4":
52 53 break;
53 54 default:
54 55 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -195,6 +195,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
195 195 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
196 196 }
197 197 break;
  198 + case "2.5.4":
  199 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  200 + log.info("Load Drop Partitions functions ...");
  201 + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
  202 + }
  203 + break;
198 204 default:
199 205 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
200 206 }
... ...
... ... @@ -177,6 +177,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
177 177 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
178 178 }
179 179 break;
  180 + case "2.5.4":
  181 + break;
180 182 default:
181 183 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
182 184 }
... ...
... ... @@ -18,7 +18,7 @@ package org.thingsboard.server.service.security.permission;
18 18 public enum Operation {
19 19
20 20 ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL,
21   - READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES,
  21 + READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES, ASSIGN_TO_TENANT,
22 22 ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE
23 23
24 24 }
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.service.state;
17 17
18   -import com.fasterxml.jackson.databind.ObjectMapper;
  18 +import com.fasterxml.jackson.databind.node.ObjectNode;
19 19 import com.google.common.base.Function;
20 20 import com.google.common.util.concurrent.FutureCallback;
21 21 import com.google.common.util.concurrent.Futures;
... ... @@ -45,16 +45,17 @@ import org.thingsboard.server.common.data.page.TextPageLink;
45 45 import org.thingsboard.server.common.msg.TbMsg;
46 46 import org.thingsboard.server.common.msg.TbMsgDataType;
47 47 import org.thingsboard.server.common.msg.TbMsgMetaData;
  48 +import org.thingsboard.server.common.msg.queue.ServiceType;
  49 +import org.thingsboard.server.common.msg.queue.TbCallback;
  50 +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
48 51 import org.thingsboard.server.dao.attributes.AttributesService;
49 52 import org.thingsboard.server.dao.device.DeviceService;
50 53 import org.thingsboard.server.dao.tenant.TenantService;
51 54 import org.thingsboard.server.dao.timeseries.TimeseriesService;
  55 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  56 +import org.thingsboard.server.gen.transport.TransportProtos;
52 57 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
53 58 import org.thingsboard.server.queue.discovery.PartitionService;
54   -import org.thingsboard.server.common.msg.queue.ServiceType;
55   -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
56   -import org.thingsboard.server.gen.transport.TransportProtos;
57   -import org.thingsboard.server.common.msg.queue.TbCallback;
58 59 import org.thingsboard.server.queue.util.TbCoreComponent;
59 60 import org.thingsboard.server.service.queue.TbClusterService;
60 61 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
... ... @@ -90,7 +91,6 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
90 91 @Slf4j
91 92 public class DefaultDeviceStateService implements DeviceStateService {
92 93
93   - private static final ObjectMapper json = new ObjectMapper();
94 94 public static final String ACTIVITY_STATE = "active";
95 95 public static final String LAST_CONNECT_TIME = "lastConnectTime";
96 96 public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime";
... ... @@ -197,15 +197,15 @@ public class DefaultDeviceStateService implements DeviceStateService {
197 197 if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) {
198 198 DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
199 199 if (stateData != null) {
200   - DeviceState state = stateData.getState();
201   - stateData.getState().setLastActivityTime(lastReportedActivity);
202   - stateData.getMetaData().putValue("scope", SERVER_SCOPE);
203   - pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
204 200 save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity);
205 201 deviceLastSavedActivity.put(deviceId, lastReportedActivity);
  202 + DeviceState state = stateData.getState();
  203 + state.setLastActivityTime(lastReportedActivity);
206 204 if (!state.isActive()) {
207 205 state.setActive(true);
208 206 save(deviceId, ACTIVITY_STATE, state.isActive());
  207 + stateData.getMetaData().putValue("scope", SERVER_SCOPE);
  208 + pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
209 209 }
210 210 }
211 211 }
... ... @@ -503,8 +503,15 @@ public class DefaultDeviceStateService implements DeviceStateService {
503 503 private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
504 504 DeviceState state = stateData.getState();
505 505 try {
506   - TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON
507   - , json.writeValueAsString(state));
  506 + String data;
  507 + if (msgType.equals(CONNECT_EVENT)) {
  508 + ObjectNode stateNode = JacksonUtil.convertValue(state, ObjectNode.class);
  509 + stateNode.remove(ACTIVITY_STATE);
  510 + data = JacksonUtil.toString(stateNode);
  511 + } else {
  512 + data = JacksonUtil.toString(state);
  513 + }
  514 + TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON, data);
508 515 clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null);
509 516 } catch (Exception e) {
510 517 log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
... ...
... ... @@ -154,7 +154,7 @@ public class DefaultTransportApiService implements TransportApiService {
154 154 return TransportApiResponseMsg.newBuilder()
155 155 .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
156 156 } catch (JsonProcessingException e) {
157   - log.warn("[{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e);
  157 + log.warn("[{}][{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e);
158 158 throw new RuntimeException(e);
159 159 } finally {
160 160 deviceCreationLock.unlock();
... ...
... ... @@ -38,19 +38,15 @@ public abstract class AbstractCleanUpService {
38 38 @Value("${spring.datasource.password}")
39 39 protected String dbPassword;
40 40
41   - protected long executeQuery(Connection conn, String query) {
42   - long removed = 0L;
43   - try {
44   - Statement statement = conn.createStatement();
  41 + protected long executeQuery(Connection conn, String query) throws SQLException {
  42 + try (Statement statement = conn.createStatement()) {
45 43 ResultSet resultSet = statement.executeQuery(query);
46   - getWarnings(statement);
  44 + if (log.isDebugEnabled()) {
  45 + getWarnings(statement);
  46 + }
47 47 resultSet.next();
48   - removed = resultSet.getLong(1);
49   - log.debug("Successfully executed query: {}", query);
50   - } catch (SQLException e) {
51   - log.debug("Failed to execute query: {} due to: {}", query, e.getMessage());
  48 + return resultSet.getLong(1);
52 49 }
53   - return removed;
54 50 }
55 51
56 52 protected void getWarnings(Statement statement) throws SQLException {
... ... @@ -65,6 +61,6 @@ public abstract class AbstractCleanUpService {
65 61 }
66 62 }
67 63
68   - protected abstract void doCleanUp(Connection connection);
  64 + protected abstract void doCleanUp(Connection connection) throws SQLException;
69 65
70 66 }
... ...
... ... @@ -49,7 +49,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService {
49 49 }
50 50
51 51 @Override
52   - protected void doCleanUp(Connection connection) {
  52 + protected void doCleanUp(Connection connection) throws SQLException {
53 53 long totalEdgeEventsRemoved = executeQuery(connection, "call cleanup_edge_events_by_ttl(" + ttl + ", 0);");
54 54 log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved);
55 55 }
... ...
... ... @@ -54,7 +54,7 @@ public class EventsCleanUpService extends AbstractCleanUpService {
54 54 }
55 55
56 56 @Override
57   - protected void doCleanUp(Connection connection) {
  57 + protected void doCleanUp(Connection connection) throws SQLException {
58 58 long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);");
59 59 log.info("Total events removed by TTL: [{}]", totalEventsRemoved);
60 60 }
... ...
... ... @@ -22,6 +22,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
22 22 import org.thingsboard.server.dao.util.PsqlTsDao;
23 23
24 24 import java.sql.Connection;
  25 +import java.sql.SQLException;
25 26
26 27 @PsqlTsDao
27 28 @Service
... ... @@ -32,7 +33,7 @@ public class PsqlTimeseriesCleanUpService extends AbstractTimeseriesCleanUpServi
32 33 private String partitionType;
33 34
34 35 @Override
35   - protected void doCleanUp(Connection connection) {
  36 + protected void doCleanUp(Connection connection) throws SQLException {
36 37 long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);");
37 38 log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved);
38 39 long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);");
... ...
... ... @@ -21,6 +21,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
21 21 import org.thingsboard.server.dao.util.TimescaleDBTsDao;
22 22
23 23 import java.sql.Connection;
  24 +import java.sql.SQLException;
24 25
25 26 @TimescaleDBTsDao
26 27 @Service
... ... @@ -28,7 +29,7 @@ import java.sql.Connection;
28 29 public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService {
29 30
30 31 @Override
31   - protected void doCleanUp(Connection connection) {
  32 + protected void doCleanUp(Connection connection) throws SQLException {
32 33 long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);");
33 34 log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
34 35 }
... ...
... ... @@ -30,6 +30,8 @@
30 30
31 31 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
32 32 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
  33 +<!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />-->
  34 +
33 35
34 36 <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
35 37
... ...
... ... @@ -589,7 +589,7 @@ transport:
589 589 edges:
590 590 rpc:
591 591 enabled: "${EDGES_RPC_ENABLED:true}"
592   - port: "${EDGES_RPC_PORT:60100}"
  592 + port: "${EDGES_RPC_PORT:7070}"
593 593 ssl:
594 594 # Enable/disable SSL support
595 595 enabled: "${EDGES_RPC_SSL_ENABLED:false}"
... ... @@ -619,6 +619,10 @@ swagger:
619 619
620 620 queue:
621 621 type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
  622 + in_memory:
  623 + stats:
  624 + # For debug lvl
  625 + print-interval-ms: "${TB_QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}"
622 626 kafka:
623 627 bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
624 628 acks: "${TB_KAFKA_ACKS:all}"
... ... @@ -630,13 +634,21 @@ queue:
630 634 max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}"
631 635 max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}"
632 636 fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
  637 + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
  638 + confluent:
  639 + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"
  640 + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
  641 + sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}"
  642 + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
  643 + other:
633 644 topic-properties:
634   - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
635   - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
636   - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
637   - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
638   - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}"
  645 + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  646 + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  647 + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  648 + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  649 + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}"
639 650 aws_sqs:
  651 + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
640 652 access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
641 653 secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
642 654 region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"
... ...
... ... @@ -79,8 +79,14 @@ import java.util.Comparator;
79 79 import java.util.List;
80 80
81 81 import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
82   -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
83   -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
  82 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
  83 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
  84 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
  85 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  86 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
  87 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  88 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
  89 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
84 90 import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
85 91
86 92 @ActiveProfiles("test")
... ... @@ -221,6 +227,7 @@ public abstract class AbstractControllerTest {
221 227 }
222 228
223 229 private Tenant savedDifferentTenant;
  230 +
224 231 protected void loginDifferentTenant() throws Exception {
225 232 loginSysAdmin();
226 233 Tenant tenant = new Tenant();
... ... @@ -316,6 +323,10 @@ public abstract class AbstractControllerTest {
316 323 return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
317 324 }
318 325
  326 + protected <T> T doGet(String urlTemplate, Class<T> responseClass, ResultMatcher resultMatcher, Object... urlVariables) throws Exception {
  327 + return readResponse(doGet(urlTemplate, urlVariables).andExpect(resultMatcher), responseClass);
  328 + }
  329 +
319 330 protected <T> T doGetAsync(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception {
320 331 return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
321 332 }
... ... @@ -357,9 +368,9 @@ public abstract class AbstractControllerTest {
357 368 return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType);
358 369 }
359 370
360   - protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType,
361   - TimePageLink pageLink,
362   - Object... urlVariables) throws Exception {
  371 + protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType,
  372 + TimePageLink pageLink,
  373 + Object... urlVariables) throws Exception {
363 374 List<Object> pageLinkVariables = new ArrayList<>();
364 375 urlTemplate += "limit={limit}";
365 376 pageLinkVariables.add(pageLink.getLimit());
... ... @@ -425,7 +436,7 @@ public abstract class AbstractControllerTest {
425 436 return mockMvc.perform(postRequest);
426 437 }
427 438
428   - protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception {
  439 + protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception {
429 440 MockHttpServletRequestBuilder postRequest = post(urlTemplate);
430 441 setJwtToken(postRequest);
431 442 String json = json(content);
... ...
... ... @@ -15,74 +15,79 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
18   -import static org.hamcrest.Matchers.containsString;
19   -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
20   -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21   -
22   -import java.util.ArrayList;
23   -import java.util.Collections;
24   -import java.util.List;
25   -
26 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.type.TypeReference;
27 20 import org.apache.commons.lang3.RandomStringUtils;
28   -import org.thingsboard.server.common.data.*;
  21 +import org.junit.After;
  22 +import org.junit.Assert;
  23 +import org.junit.Before;
  24 +import org.junit.Test;
  25 +import org.thingsboard.server.common.data.Customer;
  26 +import org.thingsboard.server.common.data.Device;
  27 +import org.thingsboard.server.common.data.EntitySubtype;
  28 +import org.thingsboard.server.common.data.Tenant;
  29 +import org.thingsboard.server.common.data.User;
29 30 import org.thingsboard.server.common.data.id.CustomerId;
30 31 import org.thingsboard.server.common.data.id.DeviceCredentialsId;
31 32 import org.thingsboard.server.common.data.id.DeviceId;
32 33 import org.thingsboard.server.common.data.page.TextPageData;
33 34 import org.thingsboard.server.common.data.page.TextPageLink;
  35 +import org.thingsboard.server.common.data.relation.EntityRelation;
  36 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
34 37 import org.thingsboard.server.common.data.security.Authority;
35 38 import org.thingsboard.server.common.data.security.DeviceCredentials;
36 39 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
37 40 import org.thingsboard.server.dao.model.ModelConstants;
38   -import org.junit.After;
39   -import org.junit.Assert;
40   -import org.junit.Before;
41   -import org.junit.Test;
42 41
43   -import com.fasterxml.jackson.core.type.TypeReference;
  42 +import java.util.ArrayList;
  43 +import java.util.Collections;
  44 +import java.util.List;
  45 +
  46 +import static org.hamcrest.Matchers.containsString;
  47 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  48 +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
44 49
45 50 public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
46   -
  51 +
47 52 private IdComparator<Device> idComparator = new IdComparator<>();
48   -
  53 +
49 54 private Tenant savedTenant;
50 55 private User tenantAdmin;
51   -
  56 +
52 57 @Before
53 58 public void beforeTest() throws Exception {
54 59 loginSysAdmin();
55   -
  60 +
56 61 Tenant tenant = new Tenant();
57 62 tenant.setTitle("My tenant");
58 63 savedTenant = doPost("/api/tenant", tenant, Tenant.class);
59 64 Assert.assertNotNull(savedTenant);
60   -
  65 +
61 66 tenantAdmin = new User();
62 67 tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
63 68 tenantAdmin.setTenantId(savedTenant.getId());
64 69 tenantAdmin.setEmail("tenant2@thingsboard.org");
65 70 tenantAdmin.setFirstName("Joe");
66 71 tenantAdmin.setLastName("Downs");
67   -
  72 +
68 73 tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
69 74 }
70   -
  75 +
71 76 @After
72 77 public void afterTest() throws Exception {
73 78 loginSysAdmin();
74   -
75   - doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
76   - .andExpect(status().isOk());
  79 +
  80 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
  81 + .andExpect(status().isOk());
77 82 }
78   -
  83 +
79 84 @Test
80 85 public void testSaveDevice() throws Exception {
81 86 Device device = new Device();
82 87 device.setName("My device");
83 88 device.setType("default");
84 89 Device savedDevice = doPost("/api/device", device, Device.class);
85   -
  90 +
86 91 Assert.assertNotNull(savedDevice);
87 92 Assert.assertNotNull(savedDevice.getId());
88 93 Assert.assertTrue(savedDevice.getCreatedTime() > 0);
... ... @@ -90,9 +95,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
90 95 Assert.assertNotNull(savedDevice.getCustomerId());
91 96 Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
92 97 Assert.assertEquals(device.getName(), savedDevice.getName());
93   -
94   - DeviceCredentials deviceCredentials =
95   - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  98 +
  99 + DeviceCredentials deviceCredentials =
  100 + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
96 101
97 102 Assert.assertNotNull(deviceCredentials);
98 103 Assert.assertNotNull(deviceCredentials.getId());
... ... @@ -100,10 +105,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
100 105 Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType());
101 106 Assert.assertNotNull(deviceCredentials.getCredentialsId());
102 107 Assert.assertEquals(20, deviceCredentials.getCredentialsId().length());
103   -
  108 +
104 109 savedDevice.setName("My new device");
105 110 doPost("/api/device", savedDevice, Device.class);
106   -
  111 +
107 112 Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
108 113 Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
109 114 }
... ... @@ -115,10 +120,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
115 120 device.setType("default");
116 121 Device savedDevice = doPost("/api/device", device, Device.class);
117 122 loginDifferentTenant();
118   - doPost("/api/device", savedDevice, Device.class, status().isForbidden());
  123 + doPost("/api/device", savedDevice, Device.class, status().isNotFound());
119 124 deleteDifferentTenant();
120 125 }
121   -
  126 +
122 127 @Test
123 128 public void testFindDeviceById() throws Exception {
124 129 Device device = new Device();
... ... @@ -133,26 +138,27 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
133 138 @Test
134 139 public void testFindDeviceTypesByTenantId() throws Exception {
135 140 List<Device> devices = new ArrayList<>();
136   - for (int i=0;i<3;i++) {
  141 + for (int i = 0; i < 3; i++) {
137 142 Device device = new Device();
138   - device.setName("My device B"+i);
  143 + device.setName("My device B" + i);
139 144 device.setType("typeB");
140 145 devices.add(doPost("/api/device", device, Device.class));
141 146 }
142   - for (int i=0;i<7;i++) {
  147 + for (int i = 0; i < 7; i++) {
143 148 Device device = new Device();
144   - device.setName("My device C"+i);
  149 + device.setName("My device C" + i);
145 150 device.setType("typeC");
146 151 devices.add(doPost("/api/device", device, Device.class));
147 152 }
148   - for (int i=0;i<9;i++) {
  153 + for (int i = 0; i < 9; i++) {
149 154 Device device = new Device();
150   - device.setName("My device A"+i);
  155 + device.setName("My device A" + i);
151 156 device.setType("typeA");
152 157 devices.add(doPost("/api/device", device, Device.class));
153 158 }
154 159 List<EntitySubtype> deviceTypes = doGetTyped("/api/device/types",
155   - new TypeReference<List<EntitySubtype>>(){});
  160 + new TypeReference<List<EntitySubtype>>() {
  161 + });
156 162
157 163 Assert.assertNotNull(deviceTypes);
158 164 Assert.assertEquals(3, deviceTypes.size());
... ... @@ -160,19 +166,19 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
160 166 Assert.assertEquals("typeB", deviceTypes.get(1).getType());
161 167 Assert.assertEquals("typeC", deviceTypes.get(2).getType());
162 168 }
163   -
  169 +
164 170 @Test
165 171 public void testDeleteDevice() throws Exception {
166 172 Device device = new Device();
167 173 device.setName("My device");
168 174 device.setType("default");
169 175 Device savedDevice = doPost("/api/device", device, Device.class);
170   -
171   - doDelete("/api/device/"+savedDevice.getId().getId().toString())
172   - .andExpect(status().isOk());
173 176
174   - doGet("/api/device/"+savedDevice.getId().getId().toString())
175   - .andExpect(status().isNotFound());
  177 + doDelete("/api/device/" + savedDevice.getId().getId().toString())
  178 + .andExpect(status().isOk());
  179 +
  180 + doGet("/api/device/" + savedDevice.getId().getId().toString())
  181 + .andExpect(status().isNotFound());
176 182 }
177 183
178 184 @Test
... ... @@ -189,52 +195,52 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
189 195 Device device = new Device();
190 196 device.setType("default");
191 197 doPost("/api/device", device)
192   - .andExpect(status().isBadRequest())
193   - .andExpect(statusReason(containsString("Device name should be specified")));
  198 + .andExpect(status().isBadRequest())
  199 + .andExpect(statusReason(containsString("Device name should be specified")));
194 200 }
195   -
  201 +
196 202 @Test
197 203 public void testAssignUnassignDeviceToCustomer() throws Exception {
198 204 Device device = new Device();
199 205 device.setName("My device");
200 206 device.setType("default");
201 207 Device savedDevice = doPost("/api/device", device, Device.class);
202   -
  208 +
203 209 Customer customer = new Customer();
204 210 customer.setTitle("My customer");
205 211 Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
206   -
207   - Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
  212 +
  213 + Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
208 214 + "/device/" + savedDevice.getId().getId().toString(), Device.class);
209 215 Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId());
210   -
  216 +
211 217 Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
212 218 Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId());
213 219
214   - Device unassignedDevice =
  220 + Device unassignedDevice =
215 221 doDelete("/api/customer/device/" + savedDevice.getId().getId().toString(), Device.class);
216 222 Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId());
217   -
  223 +
218 224 foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
219 225 Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId());
220 226 }
221   -
  227 +
222 228 @Test
223 229 public void testAssignDeviceToNonExistentCustomer() throws Exception {
224 230 Device device = new Device();
225 231 device.setName("My device");
226 232 device.setType("default");
227 233 Device savedDevice = doPost("/api/device", device, Device.class);
228   -
  234 +
229 235 doPost("/api/customer/" + UUIDs.timeBased().toString()
230 236 + "/device/" + savedDevice.getId().getId().toString())
231   - .andExpect(status().isNotFound());
  237 + .andExpect(status().isNotFound());
232 238 }
233   -
  239 +
234 240 @Test
235 241 public void testAssignDeviceToCustomerFromDifferentTenant() throws Exception {
236 242 loginSysAdmin();
237   -
  243 +
238 244 Tenant tenant2 = new Tenant();
239 245 tenant2.setTitle("Different tenant");
240 246 Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
... ... @@ -246,103 +252,103 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
246 252 tenantAdmin2.setEmail("tenant3@thingsboard.org");
247 253 tenantAdmin2.setFirstName("Joe");
248 254 tenantAdmin2.setLastName("Downs");
249   -
  255 +
250 256 tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1");
251   -
  257 +
252 258 Customer customer = new Customer();
253 259 customer.setTitle("Different customer");
254 260 Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
255 261
256 262 login(tenantAdmin.getEmail(), "testPassword1");
257   -
  263 +
258 264 Device device = new Device();
259 265 device.setName("My device");
260 266 device.setType("default");
261 267 Device savedDevice = doPost("/api/device", device, Device.class);
262   -
  268 +
263 269 doPost("/api/customer/" + savedCustomer.getId().getId().toString()
264 270 + "/device/" + savedDevice.getId().getId().toString())
265   - .andExpect(status().isForbidden());
266   -
  271 + .andExpect(status().isForbidden());
  272 +
267 273 loginSysAdmin();
268   -
269   - doDelete("/api/tenant/"+savedTenant2.getId().getId().toString())
270   - .andExpect(status().isOk());
  274 +
  275 + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
  276 + .andExpect(status().isOk());
271 277 }
272   -
  278 +
273 279 @Test
274 280 public void testFindDeviceCredentialsByDeviceId() throws Exception {
275 281 Device device = new Device();
276 282 device.setName("My device");
277 283 device.setType("default");
278 284 Device savedDevice = doPost("/api/device", device, Device.class);
279   - DeviceCredentials deviceCredentials =
280   - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  285 + DeviceCredentials deviceCredentials =
  286 + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
281 287 Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
282 288 }
283   -
  289 +
284 290 @Test
285 291 public void testSaveDeviceCredentials() throws Exception {
286 292 Device device = new Device();
287 293 device.setName("My device");
288 294 device.setType("default");
289 295 Device savedDevice = doPost("/api/device", device, Device.class);
290   - DeviceCredentials deviceCredentials =
291   - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  296 + DeviceCredentials deviceCredentials =
  297 + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
292 298 Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
293 299 deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
294 300 deviceCredentials.setCredentialsId("access_token");
295 301 doPost("/api/device/credentials", deviceCredentials)
296   - .andExpect(status().isOk());
297   -
298   - DeviceCredentials foundDeviceCredentials =
  302 + .andExpect(status().isOk());
  303 +
  304 + DeviceCredentials foundDeviceCredentials =
299 305 doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
300   -
  306 +
301 307 Assert.assertEquals(deviceCredentials, foundDeviceCredentials);
302 308 }
303   -
  309 +
304 310 @Test
305 311 public void testSaveDeviceCredentialsWithEmptyDevice() throws Exception {
306 312 DeviceCredentials deviceCredentials = new DeviceCredentials();
307 313 doPost("/api/device/credentials", deviceCredentials)
308   - .andExpect(status().isBadRequest());
  314 + .andExpect(status().isBadRequest());
309 315 }
310   -
  316 +
311 317 @Test
312 318 public void testSaveDeviceCredentialsWithEmptyCredentialsType() throws Exception {
313 319 Device device = new Device();
314 320 device.setName("My device");
315 321 device.setType("default");
316 322 Device savedDevice = doPost("/api/device", device, Device.class);
317   - DeviceCredentials deviceCredentials =
  323 + DeviceCredentials deviceCredentials =
318 324 doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
319 325 deviceCredentials.setCredentialsType(null);
320 326 doPost("/api/device/credentials", deviceCredentials)
321   - .andExpect(status().isBadRequest())
322   - .andExpect(statusReason(containsString("Device credentials type should be specified")));
  327 + .andExpect(status().isBadRequest())
  328 + .andExpect(statusReason(containsString("Device credentials type should be specified")));
323 329 }
324   -
  330 +
325 331 @Test
326 332 public void testSaveDeviceCredentialsWithEmptyCredentialsId() throws Exception {
327 333 Device device = new Device();
328 334 device.setName("My device");
329 335 device.setType("default");
330 336 Device savedDevice = doPost("/api/device", device, Device.class);
331   - DeviceCredentials deviceCredentials =
  337 + DeviceCredentials deviceCredentials =
332 338 doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
333 339 deviceCredentials.setCredentialsId(null);
334 340 doPost("/api/device/credentials", deviceCredentials)
335   - .andExpect(status().isBadRequest())
336   - .andExpect(statusReason(containsString("Device credentials id should be specified")));
  341 + .andExpect(status().isBadRequest())
  342 + .andExpect(statusReason(containsString("Device credentials id should be specified")));
337 343 }
338   -
  344 +
339 345 @Test
340 346 public void testSaveNonExistentDeviceCredentials() throws Exception {
341 347 Device device = new Device();
342 348 device.setName("My device");
343 349 device.setType("default");
344 350 Device savedDevice = doPost("/api/device", device, Device.class);
345   - DeviceCredentials deviceCredentials =
  351 + DeviceCredentials deviceCredentials =
346 352 doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
347 353 DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(UUIDs.timeBased()));
348 354 newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime());
... ... @@ -350,29 +356,29 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
350 356 newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType());
351 357 newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId());
352 358 doPost("/api/device/credentials", newDeviceCredentials)
353   - .andExpect(status().isBadRequest())
354   - .andExpect(statusReason(containsString("Unable to update non-existent device credentials")));
  359 + .andExpect(status().isBadRequest())
  360 + .andExpect(statusReason(containsString("Unable to update non-existent device credentials")));
355 361 }
356   -
  362 +
357 363 @Test
358 364 public void testSaveDeviceCredentialsWithNonExistentDevice() throws Exception {
359 365 Device device = new Device();
360 366 device.setName("My device");
361 367 device.setType("default");
362 368 Device savedDevice = doPost("/api/device", device, Device.class);
363   - DeviceCredentials deviceCredentials =
  369 + DeviceCredentials deviceCredentials =
364 370 doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
365 371 deviceCredentials.setDeviceId(new DeviceId(UUIDs.timeBased()));
366 372 doPost("/api/device/credentials", deviceCredentials)
367   - .andExpect(status().isNotFound());
  373 + .andExpect(status().isNotFound());
368 374 }
369 375
370 376 @Test
371 377 public void testFindTenantDevices() throws Exception {
372 378 List<Device> devices = new ArrayList<>();
373   - for (int i=0;i<178;i++) {
  379 + for (int i = 0; i < 178; i++) {
374 380 Device device = new Device();
375   - device.setName("Device"+i);
  381 + device.setName("Device" + i);
376 382 device.setType("default");
377 383 devices.add(doPost("/api/device", device, Device.class));
378 384 }
... ... @@ -380,28 +386,29 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
380 386 TextPageLink pageLink = new TextPageLink(23);
381 387 TextPageData<Device> pageData = null;
382 388 do {
383   - pageData = doGetTypedWithPageLink("/api/tenant/devices?",
384   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  389 + pageData = doGetTypedWithPageLink("/api/tenant/devices?",
  390 + new TypeReference<TextPageData<Device>>() {
  391 + }, pageLink);
385 392 loadedDevices.addAll(pageData.getData());
386 393 if (pageData.hasNext()) {
387 394 pageLink = pageData.getNextPageLink();
388 395 }
389 396 } while (pageData.hasNext());
390   -
  397 +
391 398 Collections.sort(devices, idComparator);
392 399 Collections.sort(loadedDevices, idComparator);
393   -
  400 +
394 401 Assert.assertEquals(devices, loadedDevices);
395 402 }
396   -
  403 +
397 404 @Test
398 405 public void testFindTenantDevicesByName() throws Exception {
399 406 String title1 = "Device title 1";
400 407 List<Device> devicesTitle1 = new ArrayList<>();
401   - for (int i=0;i<143;i++) {
  408 + for (int i = 0; i < 143; i++) {
402 409 Device device = new Device();
403 410 String suffix = RandomStringUtils.randomAlphanumeric(15);
404   - String name = title1+suffix;
  411 + String name = title1 + suffix;
405 412 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
406 413 device.setName(name);
407 414 device.setType("default");
... ... @@ -409,38 +416,40 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
409 416 }
410 417 String title2 = "Device title 2";
411 418 List<Device> devicesTitle2 = new ArrayList<>();
412   - for (int i=0;i<75;i++) {
  419 + for (int i = 0; i < 75; i++) {
413 420 Device device = new Device();
414 421 String suffix = RandomStringUtils.randomAlphanumeric(15);
415   - String name = title2+suffix;
  422 + String name = title2 + suffix;
416 423 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
417 424 device.setName(name);
418 425 device.setType("default");
419 426 devicesTitle2.add(doPost("/api/device", device, Device.class));
420 427 }
421   -
  428 +
422 429 List<Device> loadedDevicesTitle1 = new ArrayList<>();
423 430 TextPageLink pageLink = new TextPageLink(15, title1);
424 431 TextPageData<Device> pageData = null;
425 432 do {
426   - pageData = doGetTypedWithPageLink("/api/tenant/devices?",
427   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  433 + pageData = doGetTypedWithPageLink("/api/tenant/devices?",
  434 + new TypeReference<TextPageData<Device>>() {
  435 + }, pageLink);
428 436 loadedDevicesTitle1.addAll(pageData.getData());
429 437 if (pageData.hasNext()) {
430 438 pageLink = pageData.getNextPageLink();
431 439 }
432 440 } while (pageData.hasNext());
433   -
  441 +
434 442 Collections.sort(devicesTitle1, idComparator);
435 443 Collections.sort(loadedDevicesTitle1, idComparator);
436   -
  444 +
437 445 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
438   -
  446 +
439 447 List<Device> loadedDevicesTitle2 = new ArrayList<>();
440 448 pageLink = new TextPageLink(4, title2);
441 449 do {
442   - pageData = doGetTypedWithPageLink("/api/tenant/devices?",
443   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  450 + pageData = doGetTypedWithPageLink("/api/tenant/devices?",
  451 + new TypeReference<TextPageData<Device>>() {
  452 + }, pageLink);
444 453 loadedDevicesTitle2.addAll(pageData.getData());
445 454 if (pageData.hasNext()) {
446 455 pageLink = pageData.getNextPageLink();
... ... @@ -449,28 +458,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
449 458
450 459 Collections.sort(devicesTitle2, idComparator);
451 460 Collections.sort(loadedDevicesTitle2, idComparator);
452   -
  461 +
453 462 Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
454   -
  463 +
455 464 for (Device device : loadedDevicesTitle1) {
456   - doDelete("/api/device/"+device.getId().getId().toString())
457   - .andExpect(status().isOk());
  465 + doDelete("/api/device/" + device.getId().getId().toString())
  466 + .andExpect(status().isOk());
458 467 }
459   -
  468 +
460 469 pageLink = new TextPageLink(4, title1);
461   - pageData = doGetTypedWithPageLink("/api/tenant/devices?",
462   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  470 + pageData = doGetTypedWithPageLink("/api/tenant/devices?",
  471 + new TypeReference<TextPageData<Device>>() {
  472 + }, pageLink);
463 473 Assert.assertFalse(pageData.hasNext());
464 474 Assert.assertEquals(0, pageData.getData().size());
465   -
  475 +
466 476 for (Device device : loadedDevicesTitle2) {
467   - doDelete("/api/device/"+device.getId().getId().toString())
468   - .andExpect(status().isOk());
  477 + doDelete("/api/device/" + device.getId().getId().toString())
  478 + .andExpect(status().isOk());
469 479 }
470   -
  480 +
471 481 pageLink = new TextPageLink(4, title2);
472   - pageData = doGetTypedWithPageLink("/api/tenant/devices?",
473   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  482 + pageData = doGetTypedWithPageLink("/api/tenant/devices?",
  483 + new TypeReference<TextPageData<Device>>() {
  484 + }, pageLink);
474 485 Assert.assertFalse(pageData.hasNext());
475 486 Assert.assertEquals(0, pageData.getData().size());
476 487 }
... ... @@ -480,10 +491,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
480 491 String title1 = "Device title 1";
481 492 String type1 = "typeA";
482 493 List<Device> devicesType1 = new ArrayList<>();
483   - for (int i=0;i<143;i++) {
  494 + for (int i = 0; i < 143; i++) {
484 495 Device device = new Device();
485 496 String suffix = RandomStringUtils.randomAlphanumeric(15);
486   - String name = title1+suffix;
  497 + String name = title1 + suffix;
487 498 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
488 499 device.setName(name);
489 500 device.setType(type1);
... ... @@ -492,10 +503,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
492 503 String title2 = "Device title 2";
493 504 String type2 = "typeB";
494 505 List<Device> devicesType2 = new ArrayList<>();
495   - for (int i=0;i<75;i++) {
  506 + for (int i = 0; i < 75; i++) {
496 507 Device device = new Device();
497 508 String suffix = RandomStringUtils.randomAlphanumeric(15);
498   - String name = title2+suffix;
  509 + String name = title2 + suffix;
499 510 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
500 511 device.setName(name);
501 512 device.setType(type2);
... ... @@ -507,7 +518,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
507 518 TextPageData<Device> pageData = null;
508 519 do {
509 520 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
510   - new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
  521 + new TypeReference<TextPageData<Device>>() {
  522 + }, pageLink, type1);
511 523 loadedDevicesType1.addAll(pageData.getData());
512 524 if (pageData.hasNext()) {
513 525 pageLink = pageData.getNextPageLink();
... ... @@ -523,7 +535,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
523 535 pageLink = new TextPageLink(4);
524 536 do {
525 537 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
526   - new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
  538 + new TypeReference<TextPageData<Device>>() {
  539 + }, pageLink, type2);
527 540 loadedDevicesType2.addAll(pageData.getData());
528 541 if (pageData.hasNext()) {
529 542 pageLink = pageData.getNextPageLink();
... ... @@ -536,63 +549,66 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
536 549 Assert.assertEquals(devicesType2, loadedDevicesType2);
537 550
538 551 for (Device device : loadedDevicesType1) {
539   - doDelete("/api/device/"+device.getId().getId().toString())
  552 + doDelete("/api/device/" + device.getId().getId().toString())
540 553 .andExpect(status().isOk());
541 554 }
542 555
543 556 pageLink = new TextPageLink(4);
544 557 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
545   - new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
  558 + new TypeReference<TextPageData<Device>>() {
  559 + }, pageLink, type1);
546 560 Assert.assertFalse(pageData.hasNext());
547 561 Assert.assertEquals(0, pageData.getData().size());
548 562
549 563 for (Device device : loadedDevicesType2) {
550   - doDelete("/api/device/"+device.getId().getId().toString())
  564 + doDelete("/api/device/" + device.getId().getId().toString())
551 565 .andExpect(status().isOk());
552 566 }
553 567
554 568 pageLink = new TextPageLink(4);
555 569 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
556   - new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
  570 + new TypeReference<TextPageData<Device>>() {
  571 + }, pageLink, type2);
557 572 Assert.assertFalse(pageData.hasNext());
558 573 Assert.assertEquals(0, pageData.getData().size());
559 574 }
560   -
  575 +
561 576 @Test
562 577 public void testFindCustomerDevices() throws Exception {
563 578 Customer customer = new Customer();
564 579 customer.setTitle("Test customer");
565 580 customer = doPost("/api/customer", customer, Customer.class);
566 581 CustomerId customerId = customer.getId();
567   -
  582 +
568 583 List<Device> devices = new ArrayList<>();
569   - for (int i=0;i<128;i++) {
  584 + for (int i = 0; i < 128; i++) {
570 585 Device device = new Device();
571   - device.setName("Device"+i);
  586 + device.setName("Device" + i);
572 587 device.setType("default");
573 588 device = doPost("/api/device", device, Device.class);
574   - devices.add(doPost("/api/customer/" + customerId.getId().toString()
575   - + "/device/" + device.getId().getId().toString(), Device.class));
  589 + devices.add(doPost("/api/customer/" + customerId.getId().toString()
  590 + + "/device/" + device.getId().getId().toString(), Device.class));
576 591 }
577   -
  592 +
578 593 List<Device> loadedDevices = new ArrayList<>();
579 594 TextPageLink pageLink = new TextPageLink(23);
580 595 TextPageData<Device> pageData = null;
581 596 do {
582   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
583   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  597 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
  598 + new TypeReference<TextPageData<Device>>() {
  599 + }, pageLink);
584 600 loadedDevices.addAll(pageData.getData());
585 601 if (pageData.hasNext()) {
586 602 pageLink = pageData.getNextPageLink();
587 603 }
588 604 } while (pageData.hasNext());
589   -
  605 +
590 606 Collections.sort(devices, idComparator);
591 607 Collections.sort(loadedDevices, idComparator);
592   -
  608 +
593 609 Assert.assertEquals(devices, loadedDevices);
594 610 }
595   -
  611 +
596 612 @Test
597 613 public void testFindCustomerDevicesByName() throws Exception {
598 614 Customer customer = new Customer();
... ... @@ -602,53 +618,55 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
602 618
603 619 String title1 = "Device title 1";
604 620 List<Device> devicesTitle1 = new ArrayList<>();
605   - for (int i=0;i<125;i++) {
  621 + for (int i = 0; i < 125; i++) {
606 622 Device device = new Device();
607 623 String suffix = RandomStringUtils.randomAlphanumeric(15);
608   - String name = title1+suffix;
  624 + String name = title1 + suffix;
609 625 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
610 626 device.setName(name);
611 627 device.setType("default");
612 628 device = doPost("/api/device", device, Device.class);
613   - devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
  629 + devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
614 630 + "/device/" + device.getId().getId().toString(), Device.class));
615 631 }
616 632 String title2 = "Device title 2";
617 633 List<Device> devicesTitle2 = new ArrayList<>();
618   - for (int i=0;i<143;i++) {
  634 + for (int i = 0; i < 143; i++) {
619 635 Device device = new Device();
620 636 String suffix = RandomStringUtils.randomAlphanumeric(15);
621   - String name = title2+suffix;
  637 + String name = title2 + suffix;
622 638 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
623 639 device.setName(name);
624 640 device.setType("default");
625 641 device = doPost("/api/device", device, Device.class);
626   - devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
  642 + devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
627 643 + "/device/" + device.getId().getId().toString(), Device.class));
628 644 }
629   -
  645 +
630 646 List<Device> loadedDevicesTitle1 = new ArrayList<>();
631 647 TextPageLink pageLink = new TextPageLink(15, title1);
632 648 TextPageData<Device> pageData = null;
633 649 do {
634   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
635   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  650 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
  651 + new TypeReference<TextPageData<Device>>() {
  652 + }, pageLink);
636 653 loadedDevicesTitle1.addAll(pageData.getData());
637 654 if (pageData.hasNext()) {
638 655 pageLink = pageData.getNextPageLink();
639 656 }
640 657 } while (pageData.hasNext());
641   -
  658 +
642 659 Collections.sort(devicesTitle1, idComparator);
643 660 Collections.sort(loadedDevicesTitle1, idComparator);
644   -
  661 +
645 662 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
646   -
  663 +
647 664 List<Device> loadedDevicesTitle2 = new ArrayList<>();
648 665 pageLink = new TextPageLink(4, title2);
649 666 do {
650   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
651   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  667 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
  668 + new TypeReference<TextPageData<Device>>() {
  669 + }, pageLink);
652 670 loadedDevicesTitle2.addAll(pageData.getData());
653 671 if (pageData.hasNext()) {
654 672 pageLink = pageData.getNextPageLink();
... ... @@ -657,28 +675,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
657 675
658 676 Collections.sort(devicesTitle2, idComparator);
659 677 Collections.sort(loadedDevicesTitle2, idComparator);
660   -
  678 +
661 679 Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
662   -
  680 +
663 681 for (Device device : loadedDevicesTitle1) {
664 682 doDelete("/api/customer/device/" + device.getId().getId().toString())
665   - .andExpect(status().isOk());
  683 + .andExpect(status().isOk());
666 684 }
667   -
  685 +
668 686 pageLink = new TextPageLink(4, title1);
669   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
670   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  687 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
  688 + new TypeReference<TextPageData<Device>>() {
  689 + }, pageLink);
671 690 Assert.assertFalse(pageData.hasNext());
672 691 Assert.assertEquals(0, pageData.getData().size());
673   -
  692 +
674 693 for (Device device : loadedDevicesTitle2) {
675 694 doDelete("/api/customer/device/" + device.getId().getId().toString())
676   - .andExpect(status().isOk());
  695 + .andExpect(status().isOk());
677 696 }
678   -
  697 +
679 698 pageLink = new TextPageLink(4, title2);
680   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
681   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  699 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
  700 + new TypeReference<TextPageData<Device>>() {
  701 + }, pageLink);
682 702 Assert.assertFalse(pageData.hasNext());
683 703 Assert.assertEquals(0, pageData.getData().size());
684 704 }
... ... @@ -693,10 +713,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
693 713 String title1 = "Device title 1";
694 714 String type1 = "typeC";
695 715 List<Device> devicesType1 = new ArrayList<>();
696   - for (int i=0;i<125;i++) {
  716 + for (int i = 0; i < 125; i++) {
697 717 Device device = new Device();
698 718 String suffix = RandomStringUtils.randomAlphanumeric(15);
699   - String name = title1+suffix;
  719 + String name = title1 + suffix;
700 720 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
701 721 device.setName(name);
702 722 device.setType(type1);
... ... @@ -707,10 +727,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
707 727 String title2 = "Device title 2";
708 728 String type2 = "typeD";
709 729 List<Device> devicesType2 = new ArrayList<>();
710   - for (int i=0;i<143;i++) {
  730 + for (int i = 0; i < 143; i++) {
711 731 Device device = new Device();
712 732 String suffix = RandomStringUtils.randomAlphanumeric(15);
713   - String name = title2+suffix;
  733 + String name = title2 + suffix;
714 734 name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
715 735 device.setName(name);
716 736 device.setType(type2);
... ... @@ -724,7 +744,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
724 744 TextPageData<Device> pageData = null;
725 745 do {
726 746 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
727   - new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
  747 + new TypeReference<TextPageData<Device>>() {
  748 + }, pageLink, type1);
728 749 loadedDevicesType1.addAll(pageData.getData());
729 750 if (pageData.hasNext()) {
730 751 pageLink = pageData.getNextPageLink();
... ... @@ -740,7 +761,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
740 761 pageLink = new TextPageLink(4);
741 762 do {
742 763 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
743   - new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
  764 + new TypeReference<TextPageData<Device>>() {
  765 + }, pageLink, type2);
744 766 loadedDevicesType2.addAll(pageData.getData());
745 767 if (pageData.hasNext()) {
746 768 pageLink = pageData.getNextPageLink();
... ... @@ -759,7 +781,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
759 781
760 782 pageLink = new TextPageLink(4);
761 783 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
762   - new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
  784 + new TypeReference<TextPageData<Device>>() {
  785 + }, pageLink, type1);
763 786 Assert.assertFalse(pageData.hasNext());
764 787 Assert.assertEquals(0, pageData.getData().size());
765 788
... ... @@ -770,9 +793,60 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
770 793
771 794 pageLink = new TextPageLink(4);
772 795 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
773   - new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
  796 + new TypeReference<TextPageData<Device>>() {
  797 + }, pageLink, type2);
774 798 Assert.assertFalse(pageData.hasNext());
775 799 Assert.assertEquals(0, pageData.getData().size());
776 800 }
777 801
  802 + @Test
  803 + public void testAssignDeviceToTenant() throws Exception {
  804 + Device device = new Device();
  805 + device.setName("My device");
  806 + device.setType("default");
  807 + Device savedDevice = doPost("/api/device", device, Device.class);
  808 +
  809 + Device anotherDevice = new Device();
  810 + anotherDevice.setName("My device1");
  811 + anotherDevice.setType("default");
  812 + Device savedAnotherDevice = doPost("/api/device", anotherDevice, Device.class);
  813 +
  814 + EntityRelation relation = new EntityRelation();
  815 + relation.setFrom(savedDevice.getId());
  816 + relation.setTo(savedAnotherDevice.getId());
  817 + relation.setTypeGroup(RelationTypeGroup.COMMON);
  818 + relation.setType("Contains");
  819 + doPost("/api/relation", relation).andExpect(status().isOk());
  820 +
  821 + loginSysAdmin();
  822 + Tenant tenant = new Tenant();
  823 + tenant.setTitle("Different tenant");
  824 + Tenant savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
  825 + Assert.assertNotNull(savedDifferentTenant);
  826 +
  827 + User user = new User();
  828 + user.setAuthority(Authority.TENANT_ADMIN);
  829 + user.setTenantId(savedDifferentTenant.getId());
  830 + user.setEmail("tenant9@thingsboard.org");
  831 + user.setFirstName("Sam");
  832 + user.setLastName("Downs");
  833 +
  834 + createUserAndLogin(user, "testPassword1");
  835 +
  836 + login("tenant2@thingsboard.org", "testPassword1");
  837 + Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class);
  838 +
  839 + doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class, status().isNotFound());
  840 +
  841 + login("tenant9@thingsboard.org", "testPassword1");
  842 +
  843 + Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class);
  844 + Assert.assertNotNull(foundDevice1);
  845 +
  846 + doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound());
  847 +
  848 + loginSysAdmin();
  849 + doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
  850 + .andExpect(status().isOk());
  851 + }
778 852 }
... ...
... ... @@ -93,7 +93,7 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
93 93
94 94 doPost("/api/relation", relation);
95 95
96   - Thread.sleep(1000);
  96 + Thread.sleep(2000);
97 97
98 98 List<EdgeEvent> edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?",
99 99 new TypeReference<TimePageData<EdgeEvent>>() {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -16,14 +16,12 @@
16 16 package org.thingsboard.server.dao.audit;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19   -import org.thingsboard.server.common.data.BaseData;
20 19 import org.thingsboard.server.common.data.HasName;
21 20 import org.thingsboard.server.common.data.audit.ActionType;
22 21 import org.thingsboard.server.common.data.audit.AuditLog;
23 22 import org.thingsboard.server.common.data.id.CustomerId;
24 23 import org.thingsboard.server.common.data.id.EntityId;
25 24 import org.thingsboard.server.common.data.id.TenantId;
26   -import org.thingsboard.server.common.data.id.UUIDBased;
27 25 import org.thingsboard.server.common.data.id.UserId;
28 26 import org.thingsboard.server.common.data.page.TimePageData;
29 27 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -49,5 +47,4 @@ public interface AuditLogService {
49 47 E entity,
50 48 ActionType actionType,
51 49 Exception e, Object... additionalInfo);
52   -
53 50 }
... ...
... ... @@ -68,6 +68,8 @@ public interface DeviceService {
68 68
69 69 ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId);
70 70
  71 + Device assignDeviceToTenant(TenantId tenantId, Device device);
  72 +
71 73 Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
72 74
73 75 Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
... ...
... ... @@ -41,4 +41,6 @@ public interface EventService {
41 41
42 42 List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
43 43
  44 + void removeEvents(TenantId tenantId, EntityId entityId);
  45 +
44 46 }
... ...
... ... @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
24 24 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
25 25
26 26 import java.util.List;
27   -import java.util.concurrent.ExecutionException;
28 27
29 28 /**
30 29 * Created by ashvayka on 27.04.17.
... ... @@ -77,6 +76,8 @@ public interface RelationService {
77 76
78 77 ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(TenantId tenantId, EntityRelationsQuery query);
79 78
  79 + void removeRelations(TenantId tenantId, EntityId entityId);
  80 +
80 81 // TODO: This method may be useful for some validations in the future
81 82 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
82 83
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -57,6 +57,8 @@ public class DataConstants {
57 57 public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED";
58 58 public static final String ALARM_ACK = "ALARM_ACK";
59 59 public static final String ALARM_CLEAR = "ALARM_CLEAR";
  60 + public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
  61 + public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";
60 62 public static final String ENTITY_ASSIGNED_TO_EDGE = "ENTITY_ASSIGNED_TO_EDGE";
61 63 public static final String ENTITY_UNASSIGNED_FROM_EDGE = "ENTITY_UNASSIGNED_FROM_EDGE";
62 64
... ...
... ... @@ -42,6 +42,8 @@ public enum ActionType {
42 42 LOGIN(false),
43 43 LOGOUT(false),
44 44 LOCKOUT(false),
  45 + ASSIGNED_FROM_TENANT(false),
  46 + ASSIGNED_TO_TENANT(false),
45 47 ASSIGNED_TO_EDGE(false), // log edge name
46 48 UNASSIGNED_FROM_EDGE(false), // log edge name
47 49 CREDENTIALS_REQUEST(false), // request credentials from edge
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
1   -/**
2   - * Copyright © 2016-2020 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.common.msg.kv;
17   -
18   -import java.io.Serializable;
19   -import java.util.List;
20   -
21   -import org.thingsboard.server.common.data.kv.AttributeKey;
22   -import org.thingsboard.server.common.data.kv.AttributeKvEntry;
23   -
24   -public interface AttributesKVMsg extends Serializable {
25   -
26   - List<AttributeKvEntry> getClientAttributes();
27   - List<AttributeKvEntry> getSharedAttributes();
28   - List<AttributeKey> getDeletedAttributes();
29   -}
1   -/**
2   - * Copyright © 2016-2020 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.common.msg.kv;
17   -
18   -import lombok.AccessLevel;
19   -import lombok.Data;
20   -import lombok.RequiredArgsConstructor;
21   -import org.thingsboard.server.common.data.kv.AttributeKey;
22   -import org.thingsboard.server.common.data.kv.AttributeKvEntry;
23   -
24   -import java.util.Collections;
25   -import java.util.List;
26   -
27   -@Data
28   -@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
29   -public class BasicAttributeKVMsg implements AttributesKVMsg {
30   -
31   - private static final long serialVersionUID = 1L;
32   -
33   - private final List<AttributeKvEntry> clientAttributes;
34   - private final List<AttributeKvEntry> sharedAttributes;
35   - private final List<AttributeKey> deletedAttributes;
36   -
37   - public static BasicAttributeKVMsg fromClient(List<AttributeKvEntry> attributes) {
38   - return new BasicAttributeKVMsg(attributes, Collections.emptyList(), Collections.emptyList());
39   - }
40   -
41   - public static BasicAttributeKVMsg fromShared(List<AttributeKvEntry> attributes) {
42   - return new BasicAttributeKVMsg(Collections.emptyList(), attributes, Collections.emptyList());
43   - }
44   -
45   - public static BasicAttributeKVMsg from(List<AttributeKvEntry> client, List<AttributeKvEntry> shared) {
46   - return new BasicAttributeKVMsg(client, shared, Collections.emptyList());
47   - }
48   -
49   - public static AttributesKVMsg fromDeleted(List<AttributeKey> shared) {
50   - return new BasicAttributeKVMsg(Collections.emptyList(), Collections.emptyList(), shared);
51   - }
52   -}
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>common</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -37,6 +37,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
37 37 private final AdminClient client;
38 38 private final Map<String, String> topicConfigs;
39 39 private final Set<String> topics = ConcurrentHashMap.newKeySet();
  40 + private final int numPartitions;
40 41
41 42 private final short replicationFactor;
42 43
... ... @@ -50,6 +51,13 @@ public class TbKafkaAdmin implements TbQueueAdmin {
50 51 log.error("Failed to get all topics.", e);
51 52 }
52 53
  54 + String numPartitionsStr = topicConfigs.get("partitions");
  55 + if (numPartitionsStr != null) {
  56 + numPartitions = Integer.parseInt(numPartitionsStr);
  57 + topicConfigs.remove("partitions");
  58 + } else {
  59 + numPartitions = 1;
  60 + }
53 61 replicationFactor = settings.getReplicationFactor();
54 62 }
55 63
... ... @@ -59,7 +67,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
59 67 return;
60 68 }
61 69 try {
62   - NewTopic newTopic = new NewTopic(topic, 1, replicationFactor).configs(topicConfigs);
  70 + NewTopic newTopic = new NewTopic(topic, numPartitions, replicationFactor).configs(topicConfigs);
63 71 createTopic(newTopic).values().get(topic).get();
64 72 topics.add(topic);
65 73 } catch (ExecutionException ee) {
... ...
... ... @@ -16,10 +16,13 @@
16 16 package org.thingsboard.server.queue.kafka;
17 17
18 18 import lombok.Getter;
  19 +import lombok.Setter;
19 20 import lombok.extern.slf4j.Slf4j;
  21 +import org.apache.kafka.clients.CommonClientConfigs;
20 22 import org.apache.kafka.clients.producer.ProducerConfig;
21 23 import org.springframework.beans.factory.annotation.Value;
22 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  25 +import org.springframework.boot.context.properties.ConfigurationProperties;
23 26 import org.springframework.stereotype.Component;
24 27
25 28 import java.util.List;
... ... @@ -30,6 +33,7 @@ import java.util.Properties;
30 33 */
31 34 @Slf4j
32 35 @ConditionalOnExpression("'${queue.type:null}'=='kafka'")
  36 +@ConfigurationProperties(prefix = "queue.kafka")
33 37 @Component
34 38 public class TbKafkaSettings {
35 39
... ... @@ -65,20 +69,44 @@ public class TbKafkaSettings {
65 69
66 70 @Value("${queue.kafka.fetch_max_bytes:134217728}")
67 71 @Getter
68   - private int fetchMaxBytes;
  72 + private int fetchMaxBytes;
69 73
70   - @Value("${kafka.other:#{null}}")
  74 + @Value("${queue.kafka.use_confluent_cloud:false}")
  75 + private boolean useConfluent;
  76 +
  77 + @Value("${queue.kafka.confluent.ssl.algorithm}")
  78 + private String sslAlgorithm;
  79 +
  80 + @Value("${queue.kafka.confluent.sasl.mechanism}")
  81 + private String saslMechanism;
  82 +
  83 + @Value("${queue.kafka.confluent.sasl.config}")
  84 + private String saslConfig;
  85 +
  86 + @Value("${queue.kafka.confluent.security.protocol}")
  87 + private String securityProtocol;
  88 +
  89 + @Setter
71 90 private List<TbKafkaProperty> other;
72 91
73 92 public Properties toProps() {
74 93 Properties props = new Properties();
75 94 props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
76   - props.put(ProducerConfig.ACKS_CONFIG, acks);
77 95 props.put(ProducerConfig.RETRIES_CONFIG, retries);
78   - props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
79   - props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
80   - props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
81   - if(other != null){
  96 +
  97 + if (useConfluent) {
  98 + props.put("ssl.endpoint.identification.algorithm", sslAlgorithm);
  99 + props.put("sasl.mechanism", saslMechanism);
  100 + props.put("sasl.jaas.config", saslConfig);
  101 + props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
  102 + } else {
  103 + props.put(ProducerConfig.ACKS_CONFIG, acks);
  104 + props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
  105 + props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
  106 + props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
  107 + }
  108 +
  109 + if (other != null) {
82 110 other.forEach(kv -> props.put(kv.getKey(), kv.getValue()));
83 111 }
84 112 return props;
... ...
... ... @@ -24,7 +24,6 @@ import java.util.List;
24 24 import java.util.concurrent.BlockingQueue;
25 25 import java.util.concurrent.ConcurrentHashMap;
26 26 import java.util.concurrent.LinkedBlockingQueue;
27   -import java.util.concurrent.TimeUnit;
28 27
29 28 @Slf4j
30 29 public final class InMemoryStorage {
... ... @@ -35,6 +34,14 @@ public final class InMemoryStorage {
35 34 storage = new ConcurrentHashMap<>();
36 35 }
37 36
  37 + public void printStats() {
  38 + storage.forEach((topic, queue) -> {
  39 + if (queue.size() > 0) {
  40 + log.debug("[{}] Queue Size [{}]", topic, queue.size());
  41 + }
  42 + });
  43 + }
  44 +
38 45 public static InMemoryStorage getInstance() {
39 46 if (instance == null) {
40 47 synchronized (InMemoryStorage.class) {
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.queue.provider;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  20 +import org.springframework.scheduling.annotation.Scheduled;
20 21 import org.springframework.stereotype.Component;
21 22 import org.thingsboard.server.common.msg.queue.ServiceType;
22 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
... ... @@ -28,6 +29,7 @@ import org.thingsboard.server.queue.common.TbProtoJsQueueMsg;
28 29 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
29 30 import org.thingsboard.server.queue.discovery.PartitionService;
30 31 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
  32 +import org.thingsboard.server.queue.memory.InMemoryStorage;
31 33 import org.thingsboard.server.queue.memory.InMemoryTbQueueConsumer;
32 34 import org.thingsboard.server.queue.memory.InMemoryTbQueueProducer;
33 35 import org.thingsboard.server.queue.settings.TbQueueCoreSettings;
... ... @@ -47,6 +49,7 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
47 49 private final TbQueueRuleEngineSettings ruleEngineSettings;
48 50 private final TbQueueTransportApiSettings transportApiSettings;
49 51 private final TbQueueTransportNotificationSettings transportNotificationSettings;
  52 + private final InMemoryStorage storage;
50 53
51 54 public InMemoryMonolithQueueFactory(PartitionService partitionService, TbQueueCoreSettings coreSettings,
52 55 TbQueueRuleEngineSettings ruleEngineSettings,
... ... @@ -59,6 +62,7 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
59 62 this.ruleEngineSettings = ruleEngineSettings;
60 63 this.transportApiSettings = transportApiSettings;
61 64 this.transportNotificationSettings = transportNotificationSettings;
  65 + this.storage = InMemoryStorage.getInstance();
62 66 }
63 67
64 68 @Override
... ... @@ -120,4 +124,9 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
120 124 public TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate() {
121 125 return null;
122 126 }
  127 +
  128 + @Scheduled(fixedRateString = "${queue.in_memory.stats.print-interval-ms:60000}")
  129 + private void printInMemoryStats() {
  130 + storage.printStats();
  131 + }
123 132 }
... ...
... ... @@ -16,8 +16,10 @@
16 16 package org.thingsboard.server.queue.sqs;
17 17
18 18 import com.amazonaws.auth.AWSCredentials;
  19 +import com.amazonaws.auth.AWSCredentialsProvider;
19 20 import com.amazonaws.auth.AWSStaticCredentialsProvider;
20 21 import com.amazonaws.auth.BasicAWSCredentials;
  22 +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
21 23 import com.amazonaws.services.sqs.AmazonSQS;
22 24 import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
23 25 import com.amazonaws.services.sqs.model.CreateQueueRequest;
... ... @@ -37,9 +39,16 @@ public class TbAwsSqsAdmin implements TbQueueAdmin {
37 39 public TbAwsSqsAdmin(TbAwsSqsSettings sqsSettings, Map<String, String> attributes) {
38 40 this.attributes = attributes;
39 41
40   - AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
  42 + AWSCredentialsProvider credentialsProvider;
  43 + if (sqsSettings.getUseDefaultCredentialProviderChain()) {
  44 + credentialsProvider = new DefaultAWSCredentialsProviderChain();
  45 + } else {
  46 + AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
  47 + credentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
  48 + }
  49 +
41 50 sqsClient = AmazonSQSClientBuilder.standard()
42   - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
  51 + .withCredentials(credentialsProvider)
43 52 .withRegion(sqsSettings.getRegion())
44 53 .build();
45 54
... ...
... ... @@ -16,8 +16,10 @@
16 16 package org.thingsboard.server.queue.sqs;
17 17
18 18 import com.amazonaws.auth.AWSCredentials;
  19 +import com.amazonaws.auth.AWSCredentialsProvider;
19 20 import com.amazonaws.auth.AWSStaticCredentialsProvider;
20 21 import com.amazonaws.auth.BasicAWSCredentials;
  22 +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
21 23 import com.amazonaws.services.sqs.AmazonSQS;
22 24 import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
23 25 import com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry;
... ... @@ -67,13 +69,19 @@ public class TbAwsSqsConsumerTemplate<T extends TbQueueMsg> extends AbstractPara
67 69 this.decoder = decoder;
68 70 this.sqsSettings = sqsSettings;
69 71
70   - AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
71   - AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials);
  72 + AWSCredentialsProvider credentialsProvider;
  73 + if (sqsSettings.getUseDefaultCredentialProviderChain()) {
  74 + credentialsProvider = new DefaultAWSCredentialsProviderChain();
  75 + } else {
  76 + AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
  77 + credentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
  78 + }
72 79
73   - this.sqsClient = AmazonSQSClientBuilder.standard()
74   - .withCredentials(credProvider)
  80 + sqsClient = AmazonSQSClientBuilder.standard()
  81 + .withCredentials(credentialsProvider)
75 82 .withRegion(sqsSettings.getRegion())
76 83 .build();
  84 +
77 85 }
78 86
79 87 @Override
... ...
... ... @@ -16,8 +16,10 @@
16 16 package org.thingsboard.server.queue.sqs;
17 17
18 18 import com.amazonaws.auth.AWSCredentials;
  19 +import com.amazonaws.auth.AWSCredentialsProvider;
19 20 import com.amazonaws.auth.AWSStaticCredentialsProvider;
20 21 import com.amazonaws.auth.BasicAWSCredentials;
  22 +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
21 23 import com.amazonaws.services.sqs.AmazonSQS;
22 24 import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
23 25 import com.amazonaws.services.sqs.model.SendMessageRequest;
... ... @@ -54,14 +56,18 @@ public class TbAwsSqsProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
54 56 this.admin = admin;
55 57 this.defaultTopic = defaultTopic;
56 58
57   - AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
58   - AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials);
  59 + AWSCredentialsProvider credentialsProvider;
  60 + if (sqsSettings.getUseDefaultCredentialProviderChain()) {
  61 + credentialsProvider = new DefaultAWSCredentialsProviderChain();
  62 + } else {
  63 + AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
  64 + credentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
  65 + }
59 66
60   - this.sqsClient = AmazonSQSClientBuilder.standard()
61   - .withCredentials(credProvider)
  67 + sqsClient = AmazonSQSClientBuilder.standard()
  68 + .withCredentials(credentialsProvider)
62 69 .withRegion(sqsSettings.getRegion())
63 70 .build();
64   -
65 71 producerExecutor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
66 72 }
67 73
... ...
... ... @@ -27,6 +27,9 @@ import org.springframework.stereotype.Component;
27 27 @Data
28 28 public class TbAwsSqsSettings {
29 29
  30 + @Value("${queue.aws_sqs.use_default_credential_provider_chain}")
  31 + private Boolean useDefaultCredentialProviderChain;
  32 +
30 33 @Value("${queue.aws_sqs.access_key_id}")
31 34 private String accessKeyId;
32 35
... ...
... ... @@ -127,7 +127,6 @@ message GetAttributeResponseMsg {
127 127 int32 requestId = 1;
128 128 repeated TsKvProto clientAttributeList = 2;
129 129 repeated TsKvProto sharedAttributeList = 3;
130   - repeated string deletedAttributeKeys = 4;
131 130 string error = 5;
132 131 }
133 132
... ...
... ... @@ -22,7 +22,7 @@
22 22 <modelVersion>4.0.0</modelVersion>
23 23 <parent>
24 24 <groupId>org.thingsboard</groupId>
25   - <version>2.5.3-SNAPSHOT</version>
  25 + <version>2.5.5-SNAPSHOT</version>
26 26 <artifactId>common</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -125,7 +125,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
125 125
126 126 @Override
127 127 public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException {
128   - if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0 && msg.getDeletedAttributeKeysCount() == 0) {
  128 + if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) {
129 129 return new Response(CoAP.ResponseCode.NOT_FOUND);
130 130 } else {
131 131 Response response = new Response(CoAP.ResponseCode.CONTENT);
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -53,7 +53,7 @@ import java.util.concurrent.TimeUnit;
53 53 */
54 54 @Slf4j
55 55 @Component("MqttSslHandlerProvider")
56   -@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')")
  56 +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.mqtt.enabled}'=='true')")
57 57 @ConditionalOnProperty(prefix = "transport.mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
58 58 public class MqttSslHandlerProvider {
59 59
... ...
... ... @@ -207,6 +207,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
207 207 try {
208 208 return new JsonParser().parse(payload);
209 209 } catch (JsonSyntaxException ex) {
  210 + log.warn("Payload is in incorrect format: {}", payload);
210 211 throw new AdaptorException(ex);
211 212 }
212 213 }
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.kv.JsonDataEntry;
35 35 import org.thingsboard.server.common.data.kv.KvEntry;
36 36 import org.thingsboard.server.common.data.kv.LongDataEntry;
37 37 import org.thingsboard.server.common.data.kv.StringDataEntry;
38   -import org.thingsboard.server.common.msg.kv.AttributesKVMsg;
39 38 import org.thingsboard.server.gen.transport.TransportProtos;
40 39 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
41 40 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
... ... @@ -273,11 +272,6 @@ public class JsonConverter {
273 272 payload.getSharedAttributeListList().forEach(addToObjectFromProto(attrObject));
274 273 result.add("shared", attrObject);
275 274 }
276   - if (payload.getDeletedAttributeKeysCount() > 0) {
277   - JsonArray attrObject = new JsonArray();
278   - payload.getDeletedAttributeKeysList().forEach(attrObject::add);
279   - result.add("deleted", attrObject);
280   - }
281 275 return result;
282 276 }
283 277
... ... @@ -294,31 +288,6 @@ public class JsonConverter {
294 288 return result;
295 289 }
296 290
297   - public static JsonObject toJson(AttributesKVMsg payload, boolean asMap) {
298   - JsonObject result = new JsonObject();
299   - if (asMap) {
300   - if (!payload.getClientAttributes().isEmpty()) {
301   - JsonObject attrObject = new JsonObject();
302   - payload.getClientAttributes().forEach(addToObject(attrObject));
303   - result.add("client", attrObject);
304   - }
305   - if (!payload.getSharedAttributes().isEmpty()) {
306   - JsonObject attrObject = new JsonObject();
307   - payload.getSharedAttributes().forEach(addToObject(attrObject));
308   - result.add("shared", attrObject);
309   - }
310   - } else {
311   - payload.getClientAttributes().forEach(addToObject(result));
312   - payload.getSharedAttributes().forEach(addToObject(result));
313   - }
314   - if (!payload.getDeletedAttributes().isEmpty()) {
315   - JsonArray attrObject = new JsonArray();
316   - payload.getDeletedAttributes().forEach(addToObject(attrObject));
317   - result.add("deleted", attrObject);
318   - }
319   - return result;
320   - }
321   -
322 291 public static JsonObject getJsonObjectForGateway(String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) {
323 292 JsonObject result = new JsonObject();
324 293 result.addProperty("id", responseMsg.getRequestId());
... ... @@ -374,10 +343,6 @@ public class JsonConverter {
374 343 }
375 344 }
376 345
377   - private static Consumer<AttributeKey> addToObject(JsonArray result) {
378   - return key -> result.add(key.getAttributeKey());
379   - }
380   -
381 346 private static Consumer<TsKvProto> addToObjectFromProto(JsonObject result) {
382 347 return de -> {
383 348 switch (de.getKv().getType()) {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>dao</artifactId>
... ...
... ... @@ -166,7 +166,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
166 166 try {
167 167 List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get();
168 168 if (entityViews != null && !entityViews.isEmpty()) {
169   - throw new DataValidationException("Can't delete asset that is assigned to entity views!");
  169 + throw new DataValidationException("Can't delete asset that has entity views!");
170 170 }
171 171 } catch (ExecutionException | InterruptedException e) {
172 172 log.error("Exception while finding entity views for assetId [{}]", assetId, e);
... ...
... ... @@ -22,11 +22,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.EntityId;
23 23 import org.thingsboard.server.common.data.id.UserId;
24 24 import org.thingsboard.server.common.data.page.TimePageLink;
  25 +import org.thingsboard.server.dao.Dao;
25 26
26 27 import java.util.List;
27 28 import java.util.UUID;
28 29
29   -public interface AuditLogDao {
  30 +public interface AuditLogDao extends Dao<AuditLog> {
30 31
31 32 ListenableFuture<Void> saveByTenantId(AuditLog auditLog);
32 33
... ...
... ... @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired;
28 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
29 29 import org.springframework.stereotype.Service;
30 30 import org.springframework.util.StringUtils;
31   -import org.thingsboard.server.common.data.BaseData;
32 31 import org.thingsboard.server.common.data.EntityType;
33 32 import org.thingsboard.server.common.data.HasName;
34 33 import org.thingsboard.server.common.data.audit.ActionStatus;
... ... @@ -38,7 +37,6 @@ import org.thingsboard.server.common.data.id.AuditLogId;
38 37 import org.thingsboard.server.common.data.id.CustomerId;
39 38 import org.thingsboard.server.common.data.id.EntityId;
40 39 import org.thingsboard.server.common.data.id.TenantId;
41   -import org.thingsboard.server.common.data.id.UUIDBased;
42 40 import org.thingsboard.server.common.data.id.UserId;
43 41 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
44 42 import org.thingsboard.server.common.data.page.TimePageData;
... ... @@ -117,8 +115,8 @@ public class AuditLogServiceImpl implements AuditLogService {
117 115
118 116 @Override
119 117 public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>>
120   - logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
121   - ActionType actionType, Exception e, Object... additionalInfo) {
  118 + logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
  119 + ActionType actionType, Exception e, Object... additionalInfo) {
122 120 if (canLog(entityId.getEntityType(), actionType)) {
123 121 JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo);
124 122 ActionStatus actionStatus = ActionStatus.SUCCESS;
... ... @@ -129,7 +127,8 @@ public class AuditLogServiceImpl implements AuditLogService {
129 127 } else {
130 128 try {
131 129 entityName = entityService.fetchEntityNameAsync(tenantId, entityId).get();
132   - } catch (Exception ex) {}
  130 + } catch (Exception ex) {
  131 + }
133 132 }
134 133 if (e != null) {
135 134 actionStatus = ActionStatus.FAILURE;
... ... @@ -158,15 +157,16 @@ public class AuditLogServiceImpl implements AuditLogService {
158 157 }
159 158
160 159 private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity,
161   - ActionType actionType,
162   - Object... additionalInfo) {
  160 + ActionType actionType,
  161 + Object... additionalInfo) {
163 162 ObjectNode actionData = objectMapper.createObjectNode();
164   - switch(actionType) {
  163 + switch (actionType) {
165 164 case ADDED:
166 165 case UPDATED:
167 166 case ALARM_ACK:
168 167 case ALARM_CLEAR:
169 168 case RELATIONS_DELETED:
  169 + case ASSIGNED_TO_TENANT:
170 170 if (entity != null) {
171 171 ObjectNode entityNode = objectMapper.valueToTree(entity);
172 172 if (entityId.getEntityType() == EntityType.DASHBOARD) {
... ... @@ -208,7 +208,7 @@ public class AuditLogServiceImpl implements AuditLogService {
208 208 scope = extractParameter(String.class, 0, additionalInfo);
209 209 actionData.put("scope", scope);
210 210 List<String> keys = extractParameter(List.class, 1, additionalInfo);
211   - ArrayNode attrsArrayNode = actionData.putArray("attributes");
  211 + ArrayNode attrsArrayNode = actionData.putArray("attributes");
212 212 if (keys != null) {
213 213 keys.forEach(attrsArrayNode::add);
214 214 }
... ...
... ... @@ -18,14 +18,12 @@ package org.thingsboard.server.dao.audit;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
20 20 import org.springframework.stereotype.Service;
21   -import org.thingsboard.server.common.data.BaseData;
22 21 import org.thingsboard.server.common.data.HasName;
23 22 import org.thingsboard.server.common.data.audit.ActionType;
24 23 import org.thingsboard.server.common.data.audit.AuditLog;
25 24 import org.thingsboard.server.common.data.id.CustomerId;
26 25 import org.thingsboard.server.common.data.id.EntityId;
27 26 import org.thingsboard.server.common.data.id.TenantId;
28   -import org.thingsboard.server.common.data.id.UUIDBased;
29 27 import org.thingsboard.server.common.data.id.UserId;
30 28 import org.thingsboard.server.common.data.page.TimePageData;
31 29 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -60,5 +58,4 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
60 58 public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) {
61 59 return null;
62 60 }
63   -
64 61 }
... ...
... ... @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
38 38 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
39 39 import org.thingsboard.server.dao.DaoUtil;
40 40 import org.thingsboard.server.dao.model.EntitySubtypeEntity;
  41 +import org.thingsboard.server.dao.model.ModelConstants;
41 42 import org.thingsboard.server.dao.model.nosql.DeviceEntity;
42 43 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
43 44 import org.thingsboard.server.dao.relation.RelationDao;
... ... @@ -199,6 +200,21 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
199 200 }
200 201
201 202 @Override
  203 + public Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id) {
  204 + Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.TENANT_ID_PROPERTY, tenantId.getId())).and(eq(ModelConstants.ID_PROPERTY, id));
  205 + log.trace("Execute query {}", query);
  206 + DeviceEntity entity = findOneByStatement(tenantId, query);
  207 + return DaoUtil.getData(entity);
  208 + }
  209 +
  210 + @Override
  211 + public ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) {
  212 + Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.TENANT_ID_PROPERTY, tenantId.getId())).and(eq(ModelConstants.ID_PROPERTY, id));
  213 + log.trace("Execute query {}", query);
  214 + return findOneByStatementAsync(tenantId, query);
  215 + }
  216 +
  217 + @Override
202 218 public ListenableFuture<List<Device>> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
203 219 log.debug("Try to find devices by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
204 220 ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.DEVICE, pageLink);
... ...
... ... @@ -118,6 +118,23 @@ public interface DeviceDao extends Dao<Device> {
118 118 ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId);
119 119
120 120 /**
  121 + * Find devices by tenantId and device id.
  122 + * @param tenantId the tenant Id
  123 + * @param id the device Id
  124 + * @return the device object
  125 + */
  126 + Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id);
  127 +
  128 + /**
  129 + * Find devices by tenantId and device id.
  130 + * @param tenantId tenantId the tenantId
  131 + * @param id the deviceId
  132 + * @return the device object
  133 + */
  134 + ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id);
  135 +
  136 +
  137 + /**
121 138 * Find devices by tenantId, edgeId and page link.
122 139 *
123 140 * @param tenantId the tenantId
... ...
... ... @@ -28,6 +28,8 @@ import org.springframework.cache.CacheManager;
28 28 import org.springframework.cache.annotation.CacheEvict;
29 29 import org.springframework.cache.annotation.Cacheable;
30 30 import org.springframework.stereotype.Service;
  31 +import org.springframework.transaction.annotation.Transactional;
  32 +import org.springframework.util.CollectionUtils;
31 33 import org.springframework.util.StringUtils;
32 34 import org.thingsboard.server.common.data.Customer;
33 35 import org.thingsboard.server.common.data.Device;
... ... @@ -54,6 +56,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
54 56 import org.thingsboard.server.dao.customer.CustomerDao;
55 57 import org.thingsboard.server.dao.edge.EdgeService;
56 58 import org.thingsboard.server.dao.entity.AbstractEntityService;
  59 +import org.thingsboard.server.dao.event.EventService;
57 60 import org.thingsboard.server.dao.exception.DataValidationException;
58 61 import org.thingsboard.server.dao.service.DataValidator;
59 62 import org.thingsboard.server.dao.service.PaginatedRemover;
... ... @@ -104,18 +107,29 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
104 107 @Autowired
105 108 private CacheManager cacheManager;
106 109
  110 + @Autowired
  111 + private EventService eventService;
  112 +
107 113 @Override
108 114 public Device findDeviceById(TenantId tenantId, DeviceId deviceId) {
109 115 log.trace("Executing findDeviceById [{}]", deviceId);
110 116 validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
111   - return deviceDao.findById(tenantId, deviceId.getId());
  117 + if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
  118 + return deviceDao.findById(tenantId, deviceId.getId());
  119 + } else {
  120 + return deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId());
  121 + }
112 122 }
113 123
114 124 @Override
115 125 public ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId) {
116 126 log.trace("Executing findDeviceById [{}]", deviceId);
117 127 validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
118   - return deviceDao.findByIdAsync(tenantId, deviceId.getId());
  128 + if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
  129 + return deviceDao.findByIdAsync(tenantId, deviceId.getId());
  130 + } else {
  131 + return deviceDao.findDeviceByTenantIdAndIdAsync(tenantId, deviceId.getId());
  132 + }
119 133 }
120 134
121 135 @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #name}")
... ... @@ -190,7 +204,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
190 204 try {
191 205 List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
192 206 if (entityViews != null && !entityViews.isEmpty()) {
193   - throw new DataValidationException("Can't delete device that is assigned to entity views!");
  207 + throw new DataValidationException("Can't delete device that has entity views!");
194 208 }
195 209 } catch (ExecutionException | InterruptedException e) {
196 210 log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
... ... @@ -378,6 +392,31 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
378 392 }, MoreExecutors.directExecutor());
379 393 }
380 394
  395 + @Transactional
  396 + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
  397 + @Override
  398 + public Device assignDeviceToTenant(TenantId tenantId, Device device) {
  399 + log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device);
  400 +
  401 + try {
  402 + List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
  403 + if (!CollectionUtils.isEmpty(entityViews)) {
  404 + throw new DataValidationException("Can't assign device that has entity views to another tenant!");
  405 + }
  406 + } catch (ExecutionException | InterruptedException e) {
  407 + log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
  408 + throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e);
  409 + }
  410 +
  411 + eventService.removeEvents(device.getTenantId(), device.getId());
  412 +
  413 + relationService.removeRelations(device.getTenantId(), device.getId());
  414 +
  415 + device.setTenantId(tenantId);
  416 + device.setCustomerId(null);
  417 + return doSaveDevice(device, null);
  418 + }
  419 +
381 420 private DataValidator<Device> deviceValidator =
382 421 new DataValidator<Device>() {
383 422
... ...
... ... @@ -94,6 +94,21 @@ public class BaseEventService implements EventService {
94 94 return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit);
95 95 }
96 96
  97 + @Override
  98 + public void removeEvents(TenantId tenantId, EntityId entityId) {
  99 + TimePageData<Event> eventPageData;
  100 + TimePageLink eventPageLink = new TimePageLink(1000);
  101 + do {
  102 + eventPageData = findEvents(tenantId, entityId, eventPageLink);
  103 + for (Event event : eventPageData.getData()) {
  104 + eventDao.removeById(tenantId, event.getUuidId());
  105 + }
  106 + if (eventPageData.hasNext()) {
  107 + eventPageLink = eventPageData.getNextPageLink();
  108 + }
  109 + } while (eventPageData.hasNext());
  110 + }
  111 +
97 112 private DataValidator<Event> eventValidator =
98 113 new DataValidator<Event>() {
99 114 @Override
... ...
... ... @@ -506,6 +506,22 @@ public class BaseRelationService implements RelationService {
506 506 }, MoreExecutors.directExecutor());
507 507 }
508 508
  509 + @Override
  510 + public void removeRelations(TenantId tenantId, EntityId entityId) {
  511 + Cache cache = cacheManager.getCache(RELATIONS_CACHE);
  512 +
  513 + List<EntityRelation> relations = new ArrayList<>();
  514 + for (RelationTypeGroup relationTypeGroup : RelationTypeGroup.values()) {
  515 + relations.addAll(findByFrom(tenantId, entityId, relationTypeGroup));
  516 + relations.addAll(findByTo(tenantId, entityId, relationTypeGroup));
  517 + }
  518 +
  519 + for (EntityRelation relation : relations) {
  520 + cacheEviction(relation, cache);
  521 + deleteRelation(tenantId, relation);
  522 + }
  523 + }
  524 +
509 525 protected void validate(EntityRelation relation) {
510 526 if (relation == null) {
511 527 throw new DataValidationException("Relation type should be specified!");
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.dao.settings;
17 17
  18 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 19 import lombok.extern.slf4j.Slf4j;
19 20 import org.apache.commons.lang3.StringUtils;
20 21 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -52,6 +53,13 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
52 53 public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) {
53 54 log.trace("Executing saveAdminSettings [{}]", adminSettings);
54 55 adminSettingsValidator.validate(adminSettings, data -> tenantId);
  56 + if (adminSettings.getKey().equals("mail") && "".equals(adminSettings.getJsonValue().get("password").asText())) {
  57 + AdminSettings mailSettings = findAdminSettingsByKey(tenantId, "mail");
  58 + if (mailSettings != null) {
  59 + ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText());
  60 + }
  61 + }
  62 +
55 63 return adminSettingsDao.save(tenantId, adminSettings);
56 64 }
57 65
... ...
... ... @@ -85,4 +85,7 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> {
85 85 List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds);
86 86
87 87 List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds);
  88 +
  89 + DeviceEntity findByTenantIdAndId(String tenantId, String id);
  90 +
88 91 }
... ...
... ... @@ -151,6 +151,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
151 151 return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId))));
152 152 }
153 153
  154 + @Override
  155 + public Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id) {
  156 + return DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id)));
  157 + }
  158 +
  159 + @Override
  160 + public ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) {
  161 + return service.submit(() -> DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id))));
  162 + }
  163 +
154 164 private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
155 165 List<EntitySubtype> list = Collections.emptyList();
156 166 if (types != null && !types.isEmpty()) {
... ...
... ... @@ -46,6 +46,7 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
46 46 entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null));
47 47 entity.setLongValue(tsKvEntry.getLongValue().orElse(null));
48 48 entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null));
  49 + entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null));
49 50 log.trace("Saving entity: {}", entity);
50 51 return tsQueue.add(entity);
51 52 }
... ...
... ... @@ -41,8 +41,8 @@ public class HsqlLatestInsertTsRepository extends AbstractInsertRepository imple
41 41 "ON (ts_kv_latest.entity_id=T.entity_id " +
42 42 "AND ts_kv_latest.key=T.key) " +
43 43 "WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v, ts_kv_latest.json_v = T.json_v " +
44   - "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " +
45   - "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);";
  44 + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
  45 + "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);";
46 46
47 47 @Override
48 48 public void saveOrUpdate(List<TsKvLatestEntity> entities) {
... ...
... ... @@ -114,7 +114,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
114 114 public User saveUser(User user) {
115 115 log.trace("Executing saveUser [{}]", user);
116 116 userValidator.validate(user, User::getTenantId);
117   - if (user.getId() == null && !userLoginCaseSensitive) {
  117 + if (!userLoginCaseSensitive) {
118 118 user.setEmail(user.getEmail().toLowerCase());
119 119 }
120 120 User savedUser = userDao.save(user.getTenantId(), user);
... ...
... ... @@ -28,6 +28,15 @@ public class JacksonUtil {
28 28
29 29 public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
30 30
  31 + public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
  32 + try {
  33 + return OBJECT_MAPPER.convertValue(fromValue, toValueType);
  34 + } catch (IllegalArgumentException e) {
  35 + throw new IllegalArgumentException("The given object value: "
  36 + + fromValue + " cannot be converted to " + toValueType);
  37 + }
  38 + }
  39 +
31 40 public static <T> T fromString(String string, Class<T> clazz) {
32 41 try {
33 42 return OBJECT_MAPPER.readValue(string, clazz);
... ... @@ -60,4 +69,4 @@ public class JacksonUtil {
60 69 public static <T> T clone(T value) {
61 70 return fromString(toString(value), (Class<T>) value.getClass());
62 71 }
63   -}
\ No newline at end of file
  72 +}
... ...
... ... @@ -105,6 +105,7 @@ BEGIN
105 105 AND tablename like 'ts_kv_' || '%'
106 106 AND tablename != 'ts_kv_latest'
107 107 AND tablename != 'ts_kv_dictionary'
  108 + AND tablename != 'ts_kv_indefinite'
108 109 LOOP
109 110 IF partition != partition_by_max_ttl_date THEN
110 111 IF partition_year IS NOT NULL THEN
... ...
... ... @@ -13,6 +13,7 @@ DROP TABLE IF EXISTS relation;
13 13 DROP TABLE IF EXISTS tb_user;
14 14 DROP TABLE IF EXISTS tenant;
15 15 DROP TABLE IF EXISTS ts_kv;
  16 +DROP TABLE IF EXISTS ts_kv_dictionary;
16 17 DROP TABLE IF EXISTS ts_kv_latest;
17 18 DROP TABLE IF EXISTS user_credentials;
18 19 DROP TABLE IF EXISTS widget_type;
... ...
... ... @@ -39,6 +39,9 @@ function additionalComposeQueueArgs() {
39 39 kafka)
40 40 ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.kafka.yml"
41 41 ;;
  42 + confluent)
  43 + ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.confluent.yml"
  44 + ;;
42 45 aws-sqs)
43 46 ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.aws-sqs.yml"
44 47 ;;
... ... @@ -52,7 +55,7 @@ function additionalComposeQueueArgs() {
52 55 ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.service-bus.yml"
53 56 ;;
54 57 *)
55   - echo "Unknown Queue service value specified: '${TB_QUEUE_TYPE}'. Should be either kafka or aws-sqs or pubsub or rabbitmq or service-bus." >&2
  58 + echo "Unknown Queue service value specified: '${TB_QUEUE_TYPE}'. Should be either kafka or confluent or aws-sqs or pubsub or rabbitmq or service-bus." >&2
56 59 exit 1
57 60 esac
58 61 echo $ADDITIONAL_COMPOSE_QUEUE_ARGS
... ...
  1 +#
  2 +# Copyright © 2016-2020 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +version: '2.2'
  18 +
  19 +services:
  20 + tb-js-executor:
  21 + env_file:
  22 + - queue-confluent.env
  23 + tb-core1:
  24 + env_file:
  25 + - queue-confluent.env
  26 + depends_on:
  27 + - redis
  28 + tb-core2:
  29 + env_file:
  30 + - queue-confluent.env
  31 + depends_on:
  32 + - redis
  33 + tb-rule-engine1:
  34 + env_file:
  35 + - queue-confluent.env
  36 + depends_on:
  37 + - redis
  38 + tb-rule-engine2:
  39 + env_file:
  40 + - queue-confluent.env
  41 + depends_on:
  42 + - redis
  43 + tb-mqtt-transport1:
  44 + env_file:
  45 + - queue-confluent.env
  46 + tb-mqtt-transport2:
  47 + env_file:
  48 + - queue-confluent.env
  49 + tb-http-transport1:
  50 + env_file:
  51 + - queue-confluent.env
  52 + tb-http-transport2:
  53 + env_file:
  54 + - queue-confluent.env
  55 + tb-coap-transport:
  56 + env_file:
  57 + - queue-confluent.env
... ...
  1 +TB_QUEUE_TYPE=kafka
  2 +
  3 +TB_KAFKA_SERVERS=confluent.cloud:9092
  4 +TB_QUEUE_KAFKA_REPLICATION_FACTOR=3
  5 +
  6 +TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD=true
  7 +TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM=https
  8 +TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM=PLAIN
  9 +TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG=org.apache.kafka.common.security.plain.PlainLoginModule required username="CLUSTER_API_KEY" password="CLUSTER_API_SECRET";
  10 +TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL=SASL_SSL
  11 +TB_QUEUE_KAFKA_CONFLUENT_USERNAME=CLUSTER_API_KEY
  12 +TB_QUEUE_KAFKA_CONFLUENT_PASSWORD=CLUSTER_API_SECRET
  13 +
  14 +TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
  15 +TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
  16 +TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
  17 +TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000
  18 +TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:52428800;retention.bytes:104857600
... ...
1 1 TB_QUEUE_TYPE=kafka
2 2 TB_KAFKA_SERVERS=kafka:9092
  3 +TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100
... ...
... ... @@ -21,7 +21,7 @@
21 21
22 22 <parent>
23 23 <groupId>org.thingsboard</groupId>
24   - <version>2.5.3-SNAPSHOT</version>
  24 + <version>2.5.5-SNAPSHOT</version>
25 25 <artifactId>msa</artifactId>
26 26 </parent>
27 27 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -15,11 +15,15 @@
15 15 */
16 16 package org.thingsboard.server.msa;
17 17
  18 +import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.JsonMappingException;
  20 +import com.fasterxml.jackson.databind.JsonNode;
18 21 import com.fasterxml.jackson.databind.ObjectMapper;
19 22 import com.google.common.collect.ImmutableMap;
20 23 import com.google.gson.JsonArray;
21 24 import com.google.gson.JsonObject;
22 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.apache.cassandra.cql3.Json;
23 27 import org.apache.commons.lang3.RandomStringUtils;
24 28 import org.apache.http.config.Registry;
25 29 import org.apache.http.config.RegistryBuilder;
... ... @@ -32,6 +36,7 @@ import org.apache.http.impl.client.HttpClients;
32 36 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
33 37 import org.apache.http.ssl.SSLContextBuilder;
34 38 import org.apache.http.ssl.SSLContexts;
  39 +import org.json.simple.JSONObject;
35 40 import org.junit.BeforeClass;
36 41 import org.junit.Rule;
37 42 import org.junit.rules.TestRule;
... ... @@ -42,6 +47,7 @@ import org.thingsboard.rest.client.RestClient;
42 47 import org.thingsboard.server.common.data.Device;
43 48 import org.thingsboard.server.common.data.EntityType;
44 49 import org.thingsboard.server.common.data.id.DeviceId;
  50 +import org.thingsboard.server.common.data.security.DeviceCredentials;
45 51 import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
46 52
47 53
... ... @@ -52,6 +58,7 @@ import java.net.URI;
52 58 import java.security.cert.X509Certificate;
53 59 import java.util.List;
54 60 import java.util.Map;
  61 +import java.util.Optional;
55 62 import java.util.Random;
56 63
57 64 @Slf4j
... ... @@ -95,6 +102,17 @@ public abstract class AbstractContainerTest {
95 102 }
96 103 };
97 104
  105 + protected Device createGatewayDevice() throws JsonProcessingException {
  106 + String isGateway = "{\"gateway\":true}";
  107 + ObjectMapper objectMapper = new ObjectMapper();
  108 + JsonNode additionalInfo = objectMapper.readTree(isGateway);
  109 + Device gatewayDeviceTemplate = new Device();
  110 + gatewayDeviceTemplate.setName("mqtt_gateway");
  111 + gatewayDeviceTemplate.setType("gateway");
  112 + gatewayDeviceTemplate.setAdditionalInfo(additionalInfo);
  113 + return restClient.saveDevice(gatewayDeviceTemplate);
  114 + }
  115 +
98 116 protected Device createDevice(String name) {
99 117 return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT");
100 118 }
... ... @@ -140,6 +158,27 @@ public abstract class AbstractContainerTest {
140 158 return expectedValue.equals(list.get(1));
141 159 }
142 160
  161 + protected JsonObject createGatewayConnectPayload(String deviceName){
  162 + JsonObject payload = new JsonObject();
  163 + payload.addProperty("device", deviceName);
  164 + return payload;
  165 + }
  166 +
  167 + protected JsonObject createGatewayPayload(String deviceName, long ts){
  168 + JsonObject payload = new JsonObject();
  169 + payload.add(deviceName, createGatewayTelemetryArray(ts));
  170 + return payload;
  171 + }
  172 +
  173 + protected JsonArray createGatewayTelemetryArray(long ts){
  174 + JsonArray telemetryArray = new JsonArray();
  175 + if (ts > 0)
  176 + telemetryArray.add(createPayload(ts));
  177 + else
  178 + telemetryArray.add(createPayload());
  179 + return telemetryArray;
  180 + }
  181 +
143 182 protected JsonObject createPayload(long ts) {
144 183 JsonObject values = createPayload();
145 184 JsonObject payload = new JsonObject();
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.msa.connectivity;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.collect.Sets;
  21 +import com.google.common.util.concurrent.ListenableFuture;
  22 +import com.google.common.util.concurrent.ListeningExecutorService;
  23 +import com.google.common.util.concurrent.MoreExecutors;
  24 +import com.google.gson.JsonElement;
  25 +import com.google.gson.JsonObject;
  26 +import com.google.gson.JsonParser;
  27 +import io.netty.buffer.ByteBuf;
  28 +import io.netty.buffer.Unpooled;
  29 +import io.netty.handler.codec.mqtt.MqttQoS;
  30 +import lombok.Data;
  31 +import lombok.extern.slf4j.Slf4j;
  32 +import org.apache.commons.lang3.RandomStringUtils;
  33 +import org.checkerframework.checker.units.qual.A;
  34 +import org.junit.After;
  35 +import org.junit.Assert;
  36 +import org.junit.Before;
  37 +import org.junit.Test;
  38 +import org.springframework.core.ParameterizedTypeReference;
  39 +import org.springframework.http.HttpMethod;
  40 +import org.springframework.http.ResponseEntity;
  41 +import org.thingsboard.mqtt.MqttClient;
  42 +import org.thingsboard.mqtt.MqttClientConfig;
  43 +import org.thingsboard.mqtt.MqttHandler;
  44 +import org.thingsboard.server.common.data.Device;
  45 +import org.thingsboard.server.common.data.id.DeviceId;
  46 +import org.thingsboard.server.common.data.id.EntityId;
  47 +import org.thingsboard.server.common.data.id.RuleChainId;
  48 +import org.thingsboard.server.common.data.page.TextPageData;
  49 +import org.thingsboard.server.common.data.relation.EntityRelation;
  50 +import org.thingsboard.server.common.data.relation.EntityRelationInfo;
  51 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  52 +import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
  53 +import org.thingsboard.server.common.data.rule.RuleChain;
  54 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  55 +import org.thingsboard.server.common.data.rule.RuleNode;
  56 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  57 +import org.thingsboard.server.msa.AbstractContainerTest;
  58 +import org.thingsboard.server.msa.WsClient;
  59 +import org.thingsboard.server.msa.mapper.AttributesResponse;
  60 +import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
  61 +
  62 +import java.io.IOException;
  63 +import java.nio.charset.StandardCharsets;
  64 +import java.util.*;
  65 +import java.util.concurrent.*;
  66 +
  67 +@Slf4j
  68 +public class MqttGatewayClientTest extends AbstractContainerTest {
  69 + Device gatewayDevice;
  70 + MqttClient mqttClient;
  71 + Device createdDevice;
  72 + MqttMessageListener listener;
  73 +
  74 + @Before
  75 + public void createGateway() throws Exception {
  76 + restClient.login("tenant@thingsboard.org", "tenant");
  77 + this.gatewayDevice = createGatewayDevice();
  78 + Optional<DeviceCredentials> gatewayDeviceCredentials = restClient.getDeviceCredentialsByDeviceId(gatewayDevice.getId());
  79 + Assert.assertTrue(gatewayDeviceCredentials.isPresent());
  80 + this.listener = new MqttMessageListener();
  81 + this.mqttClient = getMqttClient(gatewayDeviceCredentials.get(), listener);
  82 + this.createdDevice = createDeviceThroughGateway(mqttClient, gatewayDevice);
  83 + }
  84 +
  85 + @After
  86 + public void removeGateway() throws Exception {
  87 + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.gatewayDevice.getId());
  88 + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.createdDevice.getId());
  89 + this.listener = null;
  90 + this.mqttClient = null;
  91 + this.createdDevice = null;
  92 + }
  93 +
  94 + @Test
  95 + public void telemetryUpload() throws Exception {
  96 + WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS);
  97 + mqttClient.publish("v1/gateway/telemetry", Unpooled.wrappedBuffer(createGatewayPayload(createdDevice.getName(), -1).toString().getBytes())).get();
  98 + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
  99 + log.info("Received telemetry: {}", actualLatestTelemetry);
  100 + wsClient.closeBlocking();
  101 +
  102 + Assert.assertEquals(4, actualLatestTelemetry.getData().size());
  103 + Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"),
  104 + actualLatestTelemetry.getLatestValues().keySet());
  105 +
  106 + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString()));
  107 + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1"));
  108 + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0)));
  109 + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73)));
  110 + }
  111 +
  112 + @Test
  113 + public void telemetryUploadWithTs() throws Exception {
  114 + long ts = 1451649600512L;
  115 +
  116 + restClient.login("tenant@thingsboard.org", "tenant");
  117 + WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS);
  118 + mqttClient.publish("v1/gateway/telemetry", Unpooled.wrappedBuffer(createGatewayPayload(createdDevice.getName(), ts).toString().getBytes())).get();
  119 + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
  120 + log.info("Received telemetry: {}", actualLatestTelemetry);
  121 + wsClient.closeBlocking();
  122 +
  123 + Assert.assertEquals(4, actualLatestTelemetry.getData().size());
  124 + Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues());
  125 +
  126 + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString()));
  127 + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1"));
  128 + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0)));
  129 + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73)));
  130 + }
  131 +
  132 + @Test
  133 + public void publishAttributeUpdateToServer() throws Exception {
  134 + Optional<DeviceCredentials> createdDeviceCredentials = restClient.getDeviceCredentialsByDeviceId(createdDevice.getId());
  135 + Assert.assertTrue(createdDeviceCredentials.isPresent());
  136 + WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS);
  137 + JsonObject clientAttributes = new JsonObject();
  138 + clientAttributes.addProperty("attr1", "value1");
  139 + clientAttributes.addProperty("attr2", true);
  140 + clientAttributes.addProperty("attr3", 42.0);
  141 + clientAttributes.addProperty("attr4", 73);
  142 + JsonObject gatewayClientAttributes = new JsonObject();
  143 + gatewayClientAttributes.add(createdDevice.getName(), clientAttributes);
  144 + mqttClient.publish("v1/gateway/attributes", Unpooled.wrappedBuffer(gatewayClientAttributes.toString().getBytes())).get();
  145 + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
  146 + log.info("Received attributes: {}", actualLatestTelemetry);
  147 + wsClient.closeBlocking();
  148 +
  149 + Assert.assertEquals(4, actualLatestTelemetry.getData().size());
  150 + Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"),
  151 + actualLatestTelemetry.getLatestValues().keySet());
  152 +
  153 + Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1"));
  154 + Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString()));
  155 + Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0)));
  156 + Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73)));
  157 + }
  158 +
  159 + @Test
  160 + public void requestAttributeValuesFromServer() throws Exception {
  161 + WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS);
  162 + // Add a new client attribute
  163 + JsonObject clientAttributes = new JsonObject();
  164 + String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8);
  165 + clientAttributes.addProperty("clientAttr", clientAttributeValue);
  166 +
  167 + JsonObject gatewayClientAttributes = new JsonObject();
  168 + gatewayClientAttributes.add(createdDevice.getName(), clientAttributes);
  169 + mqttClient.publish("v1/gateway/attributes", Unpooled.wrappedBuffer(gatewayClientAttributes.toString().getBytes())).get();
  170 +
  171 + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage();
  172 + log.info("Received ws telemetry: {}", actualLatestTelemetry);
  173 + wsClient.closeBlocking();
  174 +
  175 + Assert.assertEquals(1, actualLatestTelemetry.getData().size());
  176 + Assert.assertEquals(Sets.newHashSet("clientAttr"),
  177 + actualLatestTelemetry.getLatestValues().keySet());
  178 +
  179 + Assert.assertTrue(verify(actualLatestTelemetry, "clientAttr", clientAttributeValue));
  180 +
  181 + // Add a new shared attribute
  182 + JsonObject sharedAttributes = new JsonObject();
  183 + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
  184 + sharedAttributes.addProperty("sharedAttr", sharedAttributeValue);
  185 +
  186 + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate()
  187 + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
  188 + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class,
  189 + createdDevice.getId());
  190 + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful());
  191 + MqttEvent sharedAttributeEvent = listener.getEvents().poll(10, TimeUnit.SECONDS);
  192 +
  193 + // Catch attribute update event
  194 + Assert.assertNotNull(sharedAttributeEvent);
  195 + Assert.assertEquals("v1/gateway/attributes", sharedAttributeEvent.getTopic());
  196 +
  197 + // Subscribe to attributes response
  198 + mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get();
  199 +
  200 + // Wait until subscription is processed
  201 + TimeUnit.SECONDS.sleep(3);
  202 +
  203 + checkAttribute(true, clientAttributeValue);
  204 + checkAttribute(false, sharedAttributeValue);
  205 + }
  206 +
  207 + @Test
  208 + public void subscribeToAttributeUpdatesFromServer() throws Exception {
  209 + mqttClient.on("v1/gateway/attributes", listener, MqttQoS.AT_LEAST_ONCE).get();
  210 + // Wait until subscription is processed
  211 + TimeUnit.SECONDS.sleep(3);
  212 + String sharedAttributeName = "sharedAttr";
  213 + // Add a new shared attribute
  214 +
  215 + JsonObject sharedAttributes = new JsonObject();
  216 + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
  217 + sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue);
  218 +
  219 + JsonObject gatewaySharedAttributeValue = new JsonObject();
  220 + gatewaySharedAttributeValue.addProperty("device", createdDevice.getName());
  221 + gatewaySharedAttributeValue.add("data", sharedAttributes);
  222 +
  223 + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate()
  224 + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
  225 + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class,
  226 + createdDevice.getId());
  227 + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful());
  228 +
  229 + MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS);
  230 + Assert.assertEquals(sharedAttributeValue,
  231 + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText());
  232 +
  233 + // Update the shared attribute value
  234 + JsonObject updatedSharedAttributes = new JsonObject();
  235 + String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
  236 + updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue);
  237 +
  238 + JsonObject gatewayUpdatedSharedAttributeValue = new JsonObject();
  239 + gatewayUpdatedSharedAttributeValue.addProperty("device", createdDevice.getName());
  240 + gatewayUpdatedSharedAttributeValue.add("data", updatedSharedAttributes);
  241 +
  242 + ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate()
  243 + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
  244 + mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class,
  245 + createdDevice.getId());
  246 + Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful());
  247 +
  248 + event = listener.getEvents().poll(10, TimeUnit.SECONDS);
  249 + Assert.assertEquals(updatedSharedAttributeValue,
  250 + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText());
  251 + }
  252 +
  253 + @Test
  254 + public void serverSideRpc() throws Exception {
  255 + String gatewayRpcTopic = "v1/gateway/rpc";
  256 + mqttClient.on(gatewayRpcTopic, listener, MqttQoS.AT_LEAST_ONCE).get();
  257 +
  258 + // Wait until subscription is processed
  259 + TimeUnit.SECONDS.sleep(3);
  260 +
  261 + // Send an RPC from the server
  262 + JsonObject serverRpcPayload = new JsonObject();
  263 + serverRpcPayload.addProperty("method", "getValue");
  264 + serverRpcPayload.addProperty("params", true);
  265 + ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
  266 + ListenableFuture<ResponseEntity> future = service.submit(() -> {
  267 + try {
  268 + return restClient.getRestTemplate()
  269 + .postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}",
  270 + mapper.readTree(serverRpcPayload.toString()), String.class,
  271 + createdDevice.getId());
  272 + } catch (IOException e) {
  273 + return ResponseEntity.badRequest().build();
  274 + }
  275 + });
  276 +
  277 + // Wait for RPC call from the server and send the response
  278 + MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS);
  279 +
  280 + Assert.assertNotNull(requestFromServer);
  281 + Assert.assertNotNull(requestFromServer.getMessage());
  282 +
  283 + JsonObject requestFromServerJson = new JsonParser().parse(requestFromServer.getMessage()).getAsJsonObject();
  284 +
  285 + Assert.assertEquals(createdDevice.getName(), requestFromServerJson.get("device").getAsString());
  286 +
  287 + JsonObject requestFromServerData = requestFromServerJson.get("data").getAsJsonObject();
  288 +
  289 + Assert.assertEquals("getValue", requestFromServerData.get("method").getAsString());
  290 + Assert.assertTrue(requestFromServerData.get("params").getAsBoolean());
  291 +
  292 + int requestId = requestFromServerData.get("id").getAsInt();
  293 +
  294 + JsonObject clientResponse = new JsonObject();
  295 + clientResponse.addProperty("response", "someResponse");
  296 + JsonObject gatewayResponse = new JsonObject();
  297 + gatewayResponse.addProperty("device", createdDevice.getName());
  298 + gatewayResponse.addProperty("id", requestId);
  299 + gatewayResponse.add("data", clientResponse);
  300 + // Send a response to the server's RPC request
  301 +
  302 + mqttClient.publish(gatewayRpcTopic, Unpooled.wrappedBuffer(gatewayResponse.toString().getBytes())).get();
  303 + ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS);
  304 + Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful());
  305 + Assert.assertEquals(clientResponse.toString(), serverResponse.getBody());
  306 + }
  307 +
  308 + private void checkAttribute(boolean client, String expectedValue) throws Exception{
  309 + JsonObject gatewayAttributesRequest = new JsonObject();
  310 + int messageId = new Random().nextInt(100);
  311 + gatewayAttributesRequest.addProperty("id", messageId);
  312 + gatewayAttributesRequest.addProperty("device", createdDevice.getName());
  313 + gatewayAttributesRequest.addProperty("client", client);
  314 + String attributeName;
  315 + if (client)
  316 + attributeName = "clientAttr";
  317 + else
  318 + attributeName = "sharedAttr";
  319 + gatewayAttributesRequest.addProperty("key", attributeName);
  320 + log.info(gatewayAttributesRequest.toString());
  321 + mqttClient.publish("v1/gateway/attributes/request", Unpooled.wrappedBuffer(gatewayAttributesRequest.toString().getBytes())).get();
  322 + MqttEvent clientAttributeEvent = listener.getEvents().poll(10, TimeUnit.SECONDS);
  323 + Assert.assertNotNull(clientAttributeEvent);
  324 + JsonObject responseMessage = new JsonParser().parse(Objects.requireNonNull(clientAttributeEvent).getMessage()).getAsJsonObject();
  325 +
  326 + Assert.assertEquals(messageId, responseMessage.get("id").getAsInt());
  327 + Assert.assertEquals(createdDevice.getName(), responseMessage.get("device").getAsString());
  328 + Assert.assertEquals(3, responseMessage.entrySet().size());
  329 + Assert.assertEquals(expectedValue, responseMessage.get("value").getAsString());
  330 + }
  331 +
  332 + private Device createDeviceThroughGateway(MqttClient mqttClient, Device gatewayDevice) throws Exception {
  333 + String deviceName = "mqtt_device";
  334 + mqttClient.publish("v1/gateway/connect", Unpooled.wrappedBuffer(createGatewayConnectPayload(deviceName).toString().getBytes())).get();
  335 +
  336 + TimeUnit.SECONDS.sleep(3);
  337 + List<EntityRelation> relations = restClient.findByFrom(gatewayDevice.getId(), RelationTypeGroup.COMMON);
  338 +
  339 + Assert.assertEquals(1, relations.size());
  340 +
  341 + EntityId createdEntityId = relations.get(0).getTo();
  342 + DeviceId createdDeviceId = new DeviceId(createdEntityId.getId());
  343 + Optional<Device> createdDevice = restClient.getDeviceById(createdDeviceId);
  344 +
  345 + Assert.assertTrue(createdDevice.isPresent());
  346 +
  347 + return createdDevice.get();
  348 + }
  349 +
  350 + private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException {
  351 + MqttClientConfig clientConfig = new MqttClientConfig();
  352 + clientConfig.setClientId("MQTT client from test");
  353 + clientConfig.setUsername(deviceCredentials.getCredentialsId());
  354 + MqttClient mqttClient = MqttClient.create(clientConfig, listener);
  355 + mqttClient.connect("localhost", 1883).get();
  356 + return mqttClient;
  357 + }
  358 +
  359 + @Data
  360 + private class MqttMessageListener implements MqttHandler {
  361 + private final BlockingQueue<MqttEvent> events;
  362 +
  363 + private MqttMessageListener() {
  364 + events = new ArrayBlockingQueue<>(100);
  365 + }
  366 +
  367 + @Override
  368 + public void onMessage(String topic, ByteBuf message) {
  369 + log.info("MQTT message [{}], topic [{}]", message.toString(StandardCharsets.UTF_8), topic);
  370 + events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8)));
  371 + }
  372 +
  373 + }
  374 +
  375 + @Data
  376 + private class MqttEvent {
  377 + private final String topic;
  378 + private final String message;
  379 + }
  380 +
  381 +
  382 +}
... ...
... ... @@ -26,6 +26,12 @@ kafka:
26 26 servers: "TB_KAFKA_SERVERS"
27 27 replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR"
28 28 topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES"
  29 + use_confluent_cloud: "TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD"
  30 + confluent:
  31 + sasl:
  32 + mechanism: "TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM"
  33 + username: "TB_QUEUE_KAFKA_CONFLUENT_USERNAME"
  34 + password: "TB_QUEUE_KAFKA_CONFLUENT_PASSWORD"
29 35
30 36 pubsub:
31 37 project_id: "TB_QUEUE_PUBSUB_PROJECT_ID"
... ...
... ... @@ -25,7 +25,11 @@ kafka:
25 25 # Kafka Bootstrap Servers
26 26 servers: "localhost:9092"
27 27 replication_factor: "1"
28   - topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600"
  28 + topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100"
  29 + use_confluent_cloud: false
  30 + confluent:
  31 + sasl:
  32 + mechanism: "PLAIN"
29 33
30 34 pubsub:
31 35 queue_properties: "ackDeadlineInSec:30;messageRetentionInSec:604800"
... ...
... ... @@ -1872,12 +1872,14 @@
1872 1872 "balanced-match": {
1873 1873 "version": "1.0.0",
1874 1874 "bundled": true,
1875   - "dev": true
  1875 + "dev": true,
  1876 + "optional": true
1876 1877 },
1877 1878 "brace-expansion": {
1878 1879 "version": "1.1.11",
1879 1880 "bundled": true,
1880 1881 "dev": true,
  1882 + "optional": true,
1881 1883 "requires": {
1882 1884 "balanced-match": "^1.0.0",
1883 1885 "concat-map": "0.0.1"
... ... @@ -1892,17 +1894,20 @@
1892 1894 "code-point-at": {
1893 1895 "version": "1.1.0",
1894 1896 "bundled": true,
1895   - "dev": true
  1897 + "dev": true,
  1898 + "optional": true
1896 1899 },
1897 1900 "concat-map": {
1898 1901 "version": "0.0.1",
1899 1902 "bundled": true,
1900   - "dev": true
  1903 + "dev": true,
  1904 + "optional": true
1901 1905 },
1902 1906 "console-control-strings": {
1903 1907 "version": "1.1.0",
1904 1908 "bundled": true,
1905   - "dev": true
  1909 + "dev": true,
  1910 + "optional": true
1906 1911 },
1907 1912 "core-util-is": {
1908 1913 "version": "1.0.2",
... ... @@ -2019,7 +2024,8 @@
2019 2024 "inherits": {
2020 2025 "version": "2.0.3",
2021 2026 "bundled": true,
2022   - "dev": true
  2027 + "dev": true,
  2028 + "optional": true
2023 2029 },
2024 2030 "ini": {
2025 2031 "version": "1.3.5",
... ... @@ -2031,6 +2037,7 @@
2031 2037 "version": "1.0.0",
2032 2038 "bundled": true,
2033 2039 "dev": true,
  2040 + "optional": true,
2034 2041 "requires": {
2035 2042 "number-is-nan": "^1.0.0"
2036 2043 }
... ... @@ -2045,6 +2052,7 @@
2045 2052 "version": "3.0.4",
2046 2053 "bundled": true,
2047 2054 "dev": true,
  2055 + "optional": true,
2048 2056 "requires": {
2049 2057 "brace-expansion": "^1.1.7"
2050 2058 }
... ... @@ -2156,7 +2164,8 @@
2156 2164 "number-is-nan": {
2157 2165 "version": "1.0.1",
2158 2166 "bundled": true,
2159   - "dev": true
  2167 + "dev": true,
  2168 + "optional": true
2160 2169 },
2161 2170 "object-assign": {
2162 2171 "version": "4.1.1",
... ... @@ -2168,6 +2177,7 @@
2168 2177 "version": "1.4.0",
2169 2178 "bundled": true,
2170 2179 "dev": true,
  2180 + "optional": true,
2171 2181 "requires": {
2172 2182 "wrappy": "1"
2173 2183 }
... ... @@ -2289,6 +2299,7 @@
2289 2299 "version": "1.0.2",
2290 2300 "bundled": true,
2291 2301 "dev": true,
  2302 + "optional": true,
2292 2303 "requires": {
2293 2304 "code-point-at": "^1.0.0",
2294 2305 "is-fullwidth-code-point": "^1.0.0",
... ...
1 1 {
2 2 "name": "thingsboard-js-executor",
3 3 "private": true,
4   - "version": "2.5.3",
  4 + "version": "2.5.5",
5 5 "description": "ThingsBoard JavaScript Executor Microservice",
6 6 "main": "server.js",
7 7 "bin": "server.js",
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -34,7 +34,7 @@ function KafkaProducer() {
34 34 this.send = async (responseTopic, scriptId, rawResponse, headers) => {
35 35
36 36 if (!topics.includes(responseTopic)) {
37   - let createResponseTopicResult = await createTopic(responseTopic);
  37 + let createResponseTopicResult = await createTopic(responseTopic, 1);
38 38 topics.push(responseTopic);
39 39 if (createResponseTopicResult) {
40 40 logger.info('Created new topic: %s', requestTopic);
... ... @@ -61,22 +61,45 @@ function KafkaProducer() {
61 61
62 62 const kafkaBootstrapServers = config.get('kafka.bootstrap.servers');
63 63 const requestTopic = config.get('request_topic');
  64 + const useConfluent = config.get('kafka.use_confluent_cloud');
64 65
65 66 logger.info('Kafka Bootstrap Servers: %s', kafkaBootstrapServers);
66 67 logger.info('Kafka Requests Topic: %s', requestTopic);
67 68
68   - kafkaClient = new Kafka({
  69 + let kafkaConfig = {
69 70 brokers: kafkaBootstrapServers.split(','),
70   - logLevel: logLevel.INFO,
71   - logCreator: KafkaJsWinstonLogCreator
72   - });
  71 + logLevel: logLevel.INFO,
  72 + logCreator: KafkaJsWinstonLogCreator
  73 + };
  74 +
  75 + if (useConfluent) {
  76 + kafkaConfig['sasl'] = {
  77 + mechanism: config.get('kafka.confluent.sasl.mechanism'),
  78 + username: config.get('kafka.confluent.username'),
  79 + password: config.get('kafka.confluent.password')
  80 + };
  81 + kafkaConfig['ssl'] = true;
  82 + }
  83 +
  84 + kafkaClient = new Kafka(kafkaConfig);
73 85
74 86 parseTopicProperties();
75 87
76 88 kafkaAdmin = kafkaClient.admin();
77 89 await kafkaAdmin.connect();
78 90
79   - let createRequestTopicResult = await createTopic(requestTopic);
  91 + let partitions = 1;
  92 +
  93 + for (let i = 0; i < configEntries.length; i++) {
  94 + let param = configEntries[i];
  95 + if (param.name === 'partitions') {
  96 + partitions = param.value;
  97 + configEntries.splice(i, 1);
  98 + break;
  99 + }
  100 + }
  101 +
  102 + let createRequestTopicResult = await createTopic(requestTopic, partitions);
80 103
81 104 if (createRequestTopicResult) {
82 105 logger.info('Created new topic: %s', requestTopic);
... ... @@ -109,10 +132,11 @@ function KafkaProducer() {
109 132 }
110 133 })();
111 134
112   -function createTopic(topic) {
  135 +function createTopic(topic, partitions) {
113 136 return kafkaAdmin.createTopics({
114 137 topics: [{
115 138 topic: topic,
  139 + numPartitions: partitions,
116 140 replicationFactor: replicationFactor,
117 141 configEntries: configEntries
118 142 }]
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>msa</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
1 1 {
2 2 "name": "thingsboard-web-ui",
3 3 "private": true,
4   - "version": "2.5.3",
  4 + "version": "2.5.5",
5 5 "description": "ThingsBoard Web UI Microservice",
6 6 "main": "server.js",
7 7 "bin": "server.js",
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...