Commit c526d13e453cc8acd07dcca96598abc06ae6e4d6

Authored by Volodymyr Babak
2 parents 4686b232 3f9f6efc

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

Showing 130 changed files with 1488 additions and 503 deletions
... ... @@ -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>
... ...
... ... @@ -19,11 +19,11 @@
19 19 <modelVersion>4.0.0</modelVersion>
20 20 <parent>
21 21 <groupId>org.thingsboard</groupId>
22   - <version>2.5.3-SNAPSHOT</version>
  22 + <version>2.5.5-SNAPSHOT</version>
23 23 <artifactId>thingsboard</artifactId>
24 24 </parent>
25 25 <artifactId>netty-mqtt</artifactId>
26   - <version>2.5.3-SNAPSHOT</version>
  26 + <version>2.5.5-SNAPSHOT</version>
27 27 <packaging>jar</packaging>
28 28
29 29 <name>Netty MQTT Client</name>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <groupId>org.thingsboard</groupId>
22 22 <artifactId>thingsboard</artifactId>
23   - <version>2.5.3-SNAPSHOT</version>
  23 + <version>2.5.5-SNAPSHOT</version>
24 24 <packaging>pom</packaging>
25 25
26 26 <name>Thingsboard</name>
... ...
... ... @@ -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>rest-client</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>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>rule-engine</artifactId>
... ...
... ... @@ -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>rule-engine</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.rule-engine</groupId>
... ...
... ... @@ -17,11 +17,15 @@ package org.thingsboard.rule.engine.api.util;
17 17
18 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 19 import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import org.springframework.util.CollectionUtils;
20 21 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
21 22 import org.thingsboard.rule.engine.api.TbNodeException;
22 23 import org.thingsboard.server.common.msg.TbMsgMetaData;
23 24
  25 +import java.util.Collections;
  26 +import java.util.List;
24 27 import java.util.Map;
  28 +import java.util.stream.Collectors;
25 29
26 30 /**
27 31 * Created by ashvayka on 19.01.18.
... ... @@ -41,6 +45,13 @@ public class TbNodeUtils {
41 45 }
42 46 }
43 47
  48 + public static List<String> processPatterns(List<String> patterns, TbMsgMetaData metaData) {
  49 + if (!CollectionUtils.isEmpty(patterns)) {
  50 + return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList());
  51 + }
  52 + return Collections.emptyList();
  53 + }
  54 +
44 55 public static String processPattern(String pattern, TbMsgMetaData metaData) {
45 56 String result = new String(pattern);
46 57 for (Map.Entry<String,String> keyVal : metaData.values().entrySet()) {
... ...
... ... @@ -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>rule-engine</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.rule-engine</groupId>
... ...
... ... @@ -18,15 +18,16 @@ package org.thingsboard.rule.engine.action;
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21   -import com.google.common.util.concurrent.MoreExecutors;
22 21 import lombok.extern.slf4j.Slf4j;
23 22 import org.thingsboard.rule.engine.api.RuleNode;
24 23 import org.thingsboard.rule.engine.api.TbContext;
25 24 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
26 25 import org.thingsboard.rule.engine.api.TbNodeException;
27 26 import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  27 +import org.thingsboard.server.common.data.EntityType;
28 28 import org.thingsboard.server.common.data.alarm.Alarm;
29 29 import org.thingsboard.server.common.data.alarm.AlarmStatus;
  30 +import org.thingsboard.server.common.data.id.AlarmId;
30 31 import org.thingsboard.server.common.data.plugin.ComponentType;
31 32 import org.thingsboard.server.common.data.rule.RuleChainType;
32 33 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -56,8 +57,13 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfig
56 57 @Override
57 58 protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
58 59 String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg.getMetaData());
59   - ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
60   - return Futures.transformAsync(latest, a -> {
  60 + ListenableFuture<Alarm> alarmFuture;
  61 + if (msg.getOriginator().getEntityType().equals(EntityType.ALARM)) {
  62 + alarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), new AlarmId(msg.getOriginator().getId()));
  63 + } else {
  64 + alarmFuture = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
  65 + }
  66 + return Futures.transformAsync(alarmFuture, a -> {
61 67 if (a != null && !a.getStatus().isCleared()) {
62 68 return clearAlarm(ctx, msg, a);
63 69 }
... ...
... ... @@ -16,8 +16,13 @@
16 16 package org.thingsboard.rule.engine.filter;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
  20 +import org.thingsboard.rule.engine.api.RuleNode;
  21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.api.TbNode;
  23 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  24 +import org.thingsboard.rule.engine.api.TbNodeException;
19 25 import org.thingsboard.rule.engine.api.util.TbNodeUtils;
20   -import org.thingsboard.rule.engine.api.*;
21 26 import org.thingsboard.server.common.data.DataConstants;
22 27 import org.thingsboard.server.common.data.plugin.ComponentType;
23 28 import org.thingsboard.server.common.data.rule.RuleChainType;
... ... @@ -31,7 +36,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
31 36 configClazz = EmptyNodeConfiguration.class,
32 37 relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event",
33 38 "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned",
34   - "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other"},
  39 + "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant"},
35 40 nodeDescription = "Route incoming messages by Message Type",
36 41 nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> etc. via corresponding chain, otherwise <b>Other</b> chain is used.",
37 42 uiResources = {"static/rulenode/rulenode-core-config.js"},
... ... @@ -83,6 +88,10 @@ public class TbMsgTypeSwitchNode implements TbNode {
83 88 relationType = "Alarm Cleared";
84 89 } else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) {
85 90 relationType = "RPC Request to Device";
  91 + } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_FROM_TENANT)) {
  92 + relationType = "Entity Assigned From Tenant";
  93 + } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_TO_TENANT)) {
  94 + relationType = "Entity Assigned To Tenant";
86 95 } else {
87 96 relationType = "Other";
88 97 }
... ...
... ... @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbContext;
29 29 import org.thingsboard.rule.engine.api.TbNode;
30 30 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
31 31 import org.thingsboard.rule.engine.api.TbNodeException;
  32 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
32 33 import org.thingsboard.server.common.data.id.EntityId;
33 34 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
34 35 import org.thingsboard.server.common.data.kv.KvEntry;
... ... @@ -91,10 +92,10 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
91 92 }
92 93 ConcurrentHashMap<String, List<String>> failuresMap = new ConcurrentHashMap<>();
93 94 ListenableFuture<List<Void>> allFutures = Futures.allAsList(
94   - putLatestTelemetry(ctx, entityId, msg, LATEST_TS, config.getLatestTsKeyNames(), failuresMap),
95   - putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, config.getClientAttributeNames(), failuresMap, "cs_"),
96   - putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, config.getSharedAttributeNames(), failuresMap, "shared_"),
97   - putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, config.getServerAttributeNames(), failuresMap, "ss_")
  95 + putLatestTelemetry(ctx, entityId, msg, LATEST_TS, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg.getMetaData()), failuresMap),
  96 + putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg.getMetaData()), failuresMap, "cs_"),
  97 + putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg.getMetaData()), failuresMap, "shared_"),
  98 + putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg.getMetaData()), failuresMap, "ss_")
98 99 );
99 100 withCallback(allFutures, i -> {
100 101 if (!failuresMap.isEmpty()) {
... ...
... ... @@ -105,9 +105,10 @@ public class TbGetTelemetryNode implements TbNode {
105 105 if (config.isUseMetadataIntervalPatterns()) {
106 106 checkMetadataKeyPatterns(msg);
107 107 }
108   - ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(msg));
  108 + List<String> keys = TbNodeUtils.processPatterns(tsKeyNames, msg.getMetaData());
  109 + ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(msg, keys));
109 110 DonAsynchron.withCallback(list, data -> {
110   - process(data, msg);
  111 + process(data, msg, keys);
111 112 ctx.tellSuccess(ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData()));
112 113 }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor());
113 114 } catch (Exception e) {
... ... @@ -120,8 +121,8 @@ public class TbGetTelemetryNode implements TbNode {
120 121 public void destroy() {
121 122 }
122 123
123   - private List<ReadTsKvQuery> buildQueries(TbMsg msg) {
124   - return tsKeyNames.stream()
  124 + private List<ReadTsKvQuery> buildQueries(TbMsg msg, List<String> keys) {
  125 + return keys.stream()
125 126 .map(key -> new BaseReadTsKvQuery(key, getInterval(msg).getStartTs(), getInterval(msg).getEndTs(), 1, limit, NONE, getOrderBy()))
126 127 .collect(Collectors.toList());
127 128 }
... ... @@ -137,7 +138,7 @@ public class TbGetTelemetryNode implements TbNode {
137 138 }
138 139 }
139 140
140   - private void process(List<TsKvEntry> entries, TbMsg msg) {
  141 + private void process(List<TsKvEntry> entries, TbMsg msg, List<String> keys) {
141 142 ObjectNode resultNode = mapper.createObjectNode();
142 143 if (FETCH_MODE_ALL.equals(fetchMode)) {
143 144 entries.forEach(entry -> processArray(resultNode, entry));
... ... @@ -145,7 +146,7 @@ public class TbGetTelemetryNode implements TbNode {
145 146 entries.forEach(entry -> processSingle(resultNode, entry));
146 147 }
147 148
148   - for (String key : tsKeyNames) {
  149 + for (String key : keys) {
149 150 if (resultNode.has(key)) {
150 151 msg.getMetaData().putValue(key, resultNode.get(key).toString());
151 152 }
... ...
... ... @@ -35,6 +35,7 @@ import org.thingsboard.rule.engine.api.TbContext;
35 35 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
36 36 import org.thingsboard.rule.engine.api.TbNodeException;
37 37 import org.thingsboard.server.common.data.alarm.Alarm;
  38 +import org.thingsboard.server.common.data.id.AlarmId;
38 39 import org.thingsboard.server.common.data.id.DeviceId;
39 40 import org.thingsboard.server.common.data.id.EntityId;
40 41 import org.thingsboard.server.common.data.id.RuleChainId;
... ... @@ -95,6 +96,7 @@ public class TbAlarmNodeTest {
95 96 private ListeningExecutor dbExecutor;
96 97
97 98 private EntityId originator = new DeviceId(UUIDs.timeBased());
  99 + private EntityId alarmOriginator = new AlarmId(UUIDs.timeBased());
98 100 private TenantId tenantId = new TenantId(UUIDs.timeBased());
99 101 private TbMsgMetaData metaData = new TbMsgMetaData();
100 102 private String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
... ... @@ -325,6 +327,55 @@ public class TbAlarmNodeTest {
325 327 assertEquals(expectedAlarm, actualAlarm);
326 328 }
327 329
  330 + @Test
  331 + public void alarmCanBeClearedWithAlarmOriginator() throws ScriptException, IOException {
  332 + initWithClearAlarmScript();
  333 + metaData.putValue("key", "value");
  334 + TbMsg msg = TbMsg.newMsg( "USER", alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
  335 +
  336 + long oldEndDate = System.currentTimeMillis();
  337 + AlarmId id = new AlarmId(alarmOriginator.getId());
  338 + Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
  339 + activeAlarm.setId(id);
  340 +
  341 + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
  342 + when(alarmService.findAlarmByIdAsync(tenantId, id)).thenReturn(Futures.immediateFuture(activeAlarm));
  343 + when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), org.mockito.Mockito.any(JsonNode.class), anyLong())).thenReturn(Futures.immediateFuture(true));
  344 +// doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm);
  345 +
  346 + node.onMsg(ctx, msg);
  347 +
  348 + verify(ctx).tellNext(any(), eq("Cleared"));
  349 +
  350 + ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
  351 + ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class);
  352 + ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
  353 + ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
  354 + ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class);
  355 + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture());
  356 +
  357 + assertEquals("ALARM", typeCaptor.getValue());
  358 + assertEquals(alarmOriginator, originatorCaptor.getValue());
  359 + assertEquals("value", metadataCaptor.getValue().getValue("key"));
  360 + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_CLEARED_ALARM));
  361 + assertNotSame(metaData, metadataCaptor.getValue());
  362 +
  363 + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class);
  364 + Alarm expectedAlarm = Alarm.builder()
  365 + .tenantId(tenantId)
  366 + .originator(originator)
  367 + .status(CLEARED_UNACK)
  368 + .severity(WARNING)
  369 + .propagate(false)
  370 + .type("SomeType")
  371 + .details(null)
  372 + .endTs(oldEndDate)
  373 + .build();
  374 + expectedAlarm.setId(id);
  375 +
  376 + assertEquals(expectedAlarm, actualAlarm);
  377 + }
  378 +
328 379 private void initWithCreateAlarmScript() {
329 380 try {
330 381 TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration();
... ...
... ... @@ -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>tools</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>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -69,13 +69,21 @@ queue:
69 69 linger.ms: "${TB_KAFKA_LINGER_MS:1}"
70 70 buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
71 71 replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}"
  72 + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
  73 + confluent:
  74 + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"
  75 + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
  76 + 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\";}"
  77 + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
  78 + other:
72 79 topic-properties:
73   - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
74   - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
75   - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
76   - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
77   - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}"
  80 + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  81 + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  82 + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  83 + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  84 + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}"
78 85 aws_sqs:
  86 + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
79 87 access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
80 88 secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
81 89 region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"
... ...
... ... @@ -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>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -62,13 +62,21 @@ queue:
62 62 linger.ms: "${TB_KAFKA_LINGER_MS:1}"
63 63 buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
64 64 replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}"
  65 + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
  66 + confluent:
  67 + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"
  68 + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
  69 + 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\";}"
  70 + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
  71 + other:
65 72 topic-properties:
66   - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
67   - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
68   - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
69   - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
70   - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}"
  73 + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  74 + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  75 + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  76 + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  77 + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}"
71 78 aws_sqs:
  79 + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
72 80 access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
73 81 secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
74 82 region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"
... ...
... ... @@ -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>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.transport</groupId>
... ...
... ... @@ -90,13 +90,21 @@ queue:
90 90 linger.ms: "${TB_KAFKA_LINGER_MS:1}"
91 91 buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
92 92 replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}"
  93 + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}"
  94 + confluent:
  95 + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}"
  96 + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
  97 + 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\";}"
  98 + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
  99 + other:
93 100 topic-properties:
94   - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
95   - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
96   - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
97   - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
98   - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}"
  101 + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  102 + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  103 + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  104 + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  105 + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}"
99 106 aws_sqs:
  107 + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
100 108 access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
101 109 secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}"
102 110 region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}"
... ...
... ... @@ -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>transport</artifactId>
... ...
1 1 {
2 2 "name": "thingsboard",
3 3 "private": true,
4   - "version": "2.5.3",
  4 + "version": "2.5.5",
5 5 "description": "ThingsBoard UI",
6 6 "licenses": [
7 7 {
... ...
... ... @@ -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 <groupId>org.thingsboard</groupId>
... ...
... ... @@ -123,16 +123,16 @@
123 123 </md-input-container>
124 124 <md-input-container class="md-block">
125 125 <label translate>admin.proxy-password</label>
126   - <input name="proxyPassword" ng-model="vm.settings.jsonValue.proxyPassword">
  126 + <input name="proxyPassword" type="password" autocomplete="new-password" ng-model="vm.settings.jsonValue.proxyPassword">
127 127 </md-input-container>
128 128 </section>
129 129 <md-input-container class="md-block">
130 130 <label translate>common.username</label>
131   - <input name="username" placeholder="{{ 'common.enter-username' | translate }}" ng-model="vm.settings.jsonValue.username">
  131 + <input placeholder="{{ 'common.enter-username' | translate }}" ng-model="vm.settings.jsonValue.username" autocomplete="new-username" >
132 132 </md-input-container>
133 133 <md-input-container class="md-block">
134 134 <label translate>common.password</label>
135   - <input name="password" placeholder="{{ 'common.enter-password' | translate }}" type="password" ng-model="vm.settings.jsonValue.password">
  135 + <input placeholder="{{ 'common.enter-password' | translate }}" type="password" ng-model="vm.settings.jsonValue.password" autocomplete="new-password">
136 136 </md-input-container>
137 137 <div layout="row" layout-align="end center" width="100%" layout-wrap>
138 138 <md-button ng-disabled="$root.loading || vm.settingsForm.$invalid" ng-click="vm.sendTestMail()" class="md-raised">{{'admin.send-test-mail' | translate}}</md-button>
... ...
... ... @@ -228,7 +228,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
228 228 }
229 229 var deleteEntityAttributesPromise;
230 230 if (deleteAttributes.length) {
231   - deleteEntityAttributesPromise = deleteEntityAttributes(entityType, entityId, attributeScope, deleteAttributes);
  231 + deleteEntityAttributesPromise = deleteEntityAttributes(entityType, entityId, attributeScope, deleteAttributes, config);
232 232 }
233 233 if (Object.keys(attributesData).length) {
234 234 var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope;
... ...
... ... @@ -223,6 +223,12 @@ export default angular.module('thingsboard.types', [])
223 223 "LOCKOUT": {
224 224 name: "audit-log.type-lockout"
225 225 },
  226 + "ASSIGNED_FROM_TENANT": {
  227 + name: "audit-log.type-assigned-from-tenant"
  228 + },
  229 + "ASSIGNED_TO_TENANT": {
  230 + name: "audit-log.type-assigned-to-tenant"
  231 + },
226 232 "ASSIGNED_TO_EDGE": {
227 233 name: "audit-log.type-assigned-to-edge"
228 234 },
... ...
... ... @@ -150,7 +150,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
150 150 customTranslation: customTranslation,
151 151 objToBase64: objToBase64,
152 152 base64toObj: base64toObj,
153   - loadImageAspect: loadImageAspect
  153 + loadImageAspect: loadImageAspect,
  154 + sortObjectKeys: sortObjectKeys
154 155 }
155 156
156 157 return service;
... ... @@ -605,4 +606,14 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
605 606 return deferred.promise;
606 607 }
607 608
  609 + function sortObjectKeys(obj) {
  610 + var sortedObj = {};
  611 + var keys = Object.keys(obj).sort();
  612 + for (var i = 0; i < keys.length; i++) {
  613 + var key = keys[i];
  614 + sortedObj[key] = obj[key];
  615 + }
  616 + return sortedObj;
  617 + }
  618 +
608 619 }
... ...
... ... @@ -27,7 +27,7 @@ import eventRowEdgeEventTemplate from './event-row-edge-event.tpl.html';
27 27
28 28 /*@ngInject*/
29 29 export default function EventRowDirective($compile, $templateCache, $mdDialog, $document, $translate,
30   - types, toast, entityService, ruleChainService) {
  30 + types, utils, toast, entityService, ruleChainService) {
31 31
32 32 var linker = function (scope, element, attrs) {
33 33
... ... @@ -76,11 +76,18 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
76 76 if (!contentType) {
77 77 contentType = null;
78 78 }
  79 + var sortedContent;
  80 + try {
  81 + sortedContent = angular.toJson(utils.sortObjectKeys(angular.fromJson(content)));
  82 + }
  83 + catch(err) {
  84 + sortedContent = content;
  85 + }
79 86 $mdDialog.show({
80 87 controller: 'EventContentDialogController',
81 88 controllerAs: 'vm',
82 89 templateUrl: eventErrorDialogTemplate,
83   - locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback},
  90 + locals: {content: sortedContent, title: title, contentType: contentType, showingCallback: onShowingCallback},
84 91 parent: angular.element($document[0].body),
85 92 fullscreen: true,
86 93 targetEvent: $event,
... ...
... ... @@ -44,6 +44,7 @@ var ruleNodeClazzHelpLinkMap = {
44 44 'org.thingsboard.rule.engine.aws.sqs.TbSqsNode': 'ruleNodeAwsSqs',
45 45 'org.thingsboard.rule.engine.kafka.TbKafkaNode': 'ruleNodeKafka',
46 46 'org.thingsboard.rule.engine.mqtt.TbMqttNode': 'ruleNodeMqtt',
  47 + 'org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode': 'ruleNodeAzureIotHub',
47 48 'org.thingsboard.rule.engine.rabbitmq.TbRabbitMqNode': 'ruleNodeRabbitMq',
48 49 'org.thingsboard.rule.engine.rest.TbRestApiCallNode': 'ruleNodeRestApiCall',
49 50 'org.thingsboard.rule.engine.mail.TbSendEmailNode': 'ruleNodeSendEmail'
... ... @@ -88,6 +89,7 @@ export default angular.module('thingsboard.help', [])
88 89 ruleNodeAwsSqs: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/external-nodes/#aws-sqs-node",
89 90 ruleNodeKafka: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/external-nodes/#kafka-node",
90 91 ruleNodeMqtt: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/external-nodes/#mqtt-node",
  92 + ruleNodeAzureIotHub: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/external-nodes/#azure-iot-hub-node",
91 93 ruleNodeRabbitMq: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/external-nodes/#rabbitmq-node",
92 94 ruleNodeRestApiCall: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/external-nodes/#rest-api-call-node",
93 95 ruleNodeSendEmail: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/external-nodes/#send-email-node",
... ...
... ... @@ -373,7 +373,9 @@
373 373 "action-data": "Action data",
374 374 "failure-details": "Failure details",
375 375 "search": "Search audit logs",
376   - "clear-search": "Clear search"
  376 + "clear-search": "Clear search",
  377 + "type-assigned-from-tenant": "Assigned from Tenant",
  378 + "type-assigned-to-tenant": "Assigned to Tenant"
377 379 },
378 380 "confirm-on-exit": {
379 381 "message": "You have unsaved changes. Are you sure you want to leave this page?",
... ...
... ... @@ -20,7 +20,7 @@ import nodeScriptTestTemplate from './node-script-test.tpl.html';
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 22 /*@ngInject*/
23   -export default function NodeScriptTest($q, $mdDialog, $document, ruleChainService) {
  23 +export default function NodeScriptTest($q, $mdDialog, $document, ruleChainService, utils) {
24 24
25 25 var service = {
26 26 testNodeScript: testNodeScript
... ... @@ -89,6 +89,8 @@ export default function NodeScriptTest($q, $mdDialog, $document, ruleChainServic
89 89 deviceName: "Test Device",
90 90 ts: new Date().getTime() + ""
91 91 };
  92 + } else {
  93 + metadata = utils.sortObjectKeys(metadata);
92 94 }
93 95 if (!msgType) {
94 96 msgType = "POST_TELEMETRY_REQUEST";
... ...