Commit c526d13e453cc8acd07dcca96598abc06ae6e4d6
Merge remote-tracking branch 'upstream/develop/2.5.5' into feature/edge
Showing
100 changed files
with
1302 additions
and
450 deletions
Too many changes to show.
To preserve performance only 100 of 130 files are displayed.
... | ... | @@ -20,7 +20,7 @@ |
20 | 20 | <modelVersion>4.0.0</modelVersion> |
21 | 21 | <parent> |
22 | 22 | <groupId>org.thingsboard</groupId> |
23 | - <version>2.5.3-SNAPSHOT</version> | |
23 | + <version>2.5.5-SNAPSHOT</version> | |
24 | 24 | <artifactId>thingsboard</artifactId> |
25 | 25 | </parent> |
26 | 26 | <artifactId>application</artifactId> | ... | ... |
... | ... | @@ -64,6 +64,7 @@ BEGIN |
64 | 64 | AND tablename like 'ts_kv_' || '%' |
65 | 65 | AND tablename != 'ts_kv_latest' |
66 | 66 | AND tablename != 'ts_kv_dictionary' |
67 | + AND tablename != 'ts_kv_indefinite' | |
67 | 68 | LOOP |
68 | 69 | IF partition != partition_by_max_ttl_date THEN |
69 | 70 | IF partition_year IS NOT NULL THEN | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; |
28 | 28 | import org.thingsboard.server.actors.ActorSystemContext; |
29 | 29 | import org.thingsboard.server.actors.TbActorCtx; |
30 | 30 | import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; |
31 | +import org.thingsboard.server.common.data.DataConstants; | |
31 | 32 | import org.thingsboard.server.common.data.Device; |
32 | 33 | import org.thingsboard.server.common.data.id.DeviceId; |
33 | 34 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -79,8 +80,6 @@ import java.util.UUID; |
79 | 80 | import java.util.function.Consumer; |
80 | 81 | import java.util.stream.Collectors; |
81 | 82 | |
82 | -import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; | |
83 | -import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; | |
84 | 83 | |
85 | 84 | /** |
86 | 85 | * @author Andrew Shvayka |
... | ... | @@ -279,17 +278,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
279 | 278 | ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture; |
280 | 279 | ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture; |
281 | 280 | if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { |
282 | - clientAttributesFuture = findAllAttributesByScope(CLIENT_SCOPE); | |
283 | - sharedAttributesFuture = findAllAttributesByScope(SHARED_SCOPE); | |
281 | + clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE); | |
282 | + sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE); | |
284 | 283 | } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { |
285 | - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE); | |
286 | - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE); | |
284 | + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); | |
285 | + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); | |
287 | 286 | } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { |
288 | 287 | clientAttributesFuture = Futures.immediateFuture(Collections.emptyList()); |
289 | - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE); | |
288 | + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); | |
290 | 289 | } else { |
291 | 290 | sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList()); |
292 | - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE); | |
291 | + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); | |
293 | 292 | } |
294 | 293 | return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)); |
295 | 294 | } |
... | ... | @@ -316,7 +315,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
316 | 315 | AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder(); |
317 | 316 | if (msg.isDeleted()) { |
318 | 317 | List<String> sharedKeys = msg.getDeletedKeys().stream() |
319 | - .filter(key -> SHARED_SCOPE.equals(key.getScope())) | |
318 | + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) | |
320 | 319 | .map(AttributeKey::getAttributeKey) |
321 | 320 | .collect(Collectors.toList()); |
322 | 321 | if (!sharedKeys.isEmpty()) { |
... | ... | @@ -324,7 +323,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
324 | 323 | hasNotificationData = true; |
325 | 324 | } |
326 | 325 | } else { |
327 | - if (SHARED_SCOPE.equals(msg.getScope())) { | |
326 | + if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) { | |
328 | 327 | List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues()); |
329 | 328 | if (attributes.size() > 0) { |
330 | 329 | List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto) |
... | ... | @@ -334,7 +333,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { |
334 | 333 | hasNotificationData = true; |
335 | 334 | } |
336 | 335 | } else { |
337 | - log.debug("[{}] No public server side attributes changed!", deviceId); | |
336 | + log.debug("[{}] No public shared side attributes changed!", deviceId); | |
338 | 337 | } |
339 | 338 | } |
340 | 339 | } | ... | ... |
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
18 | 19 | import org.springframework.beans.factory.annotation.Autowired; |
19 | 20 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | 21 | import org.springframework.web.bind.annotation.PathVariable; |
... | ... | @@ -59,7 +60,11 @@ public class AdminController extends BaseController { |
59 | 60 | public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException { |
60 | 61 | try { |
61 | 62 | accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); |
62 | - return checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key)); | |
63 | + AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key)); | |
64 | + if (adminSettings.getKey().equals("mail")) { | |
65 | + ((ObjectNode) adminSettings.getJsonValue()).put("password", ""); | |
66 | + } | |
67 | + return adminSettings; | |
63 | 68 | } catch (Exception e) { |
64 | 69 | throw handleException(e); |
65 | 70 | } |
... | ... | @@ -74,6 +79,7 @@ public class AdminController extends BaseController { |
74 | 79 | adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); |
75 | 80 | if (adminSettings.getKey().equals("mail")) { |
76 | 81 | mailService.updateMailConfiguration(); |
82 | + ((ObjectNode) adminSettings.getJsonValue()).put("password", ""); | |
77 | 83 | } |
78 | 84 | return adminSettings; |
79 | 85 | } catch (Exception e) { | ... | ... |
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import com.fasterxml.jackson.core.JsonProcessingException; | |
18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
19 | 20 | import com.fasterxml.jackson.databind.node.ArrayNode; |
20 | 21 | import com.fasterxml.jackson.databind.node.ObjectNode; |
... | ... | @@ -624,6 +625,12 @@ public abstract class BaseController { |
624 | 625 | case ALARM_CLEAR: |
625 | 626 | msgType = DataConstants.ALARM_CLEAR; |
626 | 627 | break; |
628 | + case ASSIGNED_FROM_TENANT: | |
629 | + msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT; | |
630 | + break; | |
631 | + case ASSIGNED_TO_TENANT: | |
632 | + msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; | |
633 | + break; | |
627 | 634 | case ASSIGNED_TO_EDGE: |
628 | 635 | msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE; |
629 | 636 | break; |
... | ... | @@ -649,7 +656,17 @@ public abstract class BaseController { |
649 | 656 | String strCustomerName = extractParameter(String.class, 2, additionalInfo); |
650 | 657 | metaData.putValue("unassignedCustomerId", strCustomerId); |
651 | 658 | metaData.putValue("unassignedCustomerName", strCustomerName); |
652 | - } if (actionType == ActionType.ASSIGNED_TO_EDGE) { | |
659 | + } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) { | |
660 | + String strTenantId = extractParameter(String.class, 0, additionalInfo); | |
661 | + String strTenantName = extractParameter(String.class, 1, additionalInfo); | |
662 | + metaData.putValue("assignedFromTenantId", strTenantId); | |
663 | + metaData.putValue("assignedFromTenantName", strTenantName); | |
664 | + } else if (actionType == ActionType.ASSIGNED_TO_TENANT) { | |
665 | + String strTenantId = extractParameter(String.class, 0, additionalInfo); | |
666 | + String strTenantName = extractParameter(String.class, 1, additionalInfo); | |
667 | + metaData.putValue("assignedToTenantId", strTenantId); | |
668 | + metaData.putValue("assignedToTenantName", strTenantName); | |
669 | + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) { | |
653 | 670 | String strEdgeId = extractParameter(String.class, 1, additionalInfo); |
654 | 671 | metaData.putValue("assignedEdgeId", strEdgeId); |
655 | 672 | } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) { |
... | ... | @@ -718,6 +735,15 @@ public abstract class BaseController { |
718 | 735 | return result; |
719 | 736 | } |
720 | 737 | |
738 | + protected <E extends HasName> String entityToStr(E entity) { | |
739 | + try { | |
740 | + return json.writeValueAsString(json.valueToTree(entity)); | |
741 | + } catch (JsonProcessingException e) { | |
742 | + log.warn("[{}] Failed to convert entity to string!", entity, e); | |
743 | + } | |
744 | + return null; | |
745 | + } | |
746 | + | |
721 | 747 | protected void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, CustomerId customerId, ActionType edgeEventAction) { |
722 | 748 | try { |
723 | 749 | sendNotificationMsgToEdgeService(tenantId, edgeId, null, json.writeValueAsString(customerId), EdgeEventType.EDGE, edgeEventAction); | ... | ... |
... | ... | @@ -39,10 +39,11 @@ import org.thingsboard.server.common.data.DataConstants; |
39 | 39 | import org.thingsboard.server.common.data.Device; |
40 | 40 | import org.thingsboard.server.common.data.EntitySubtype; |
41 | 41 | import org.thingsboard.server.common.data.EntityType; |
42 | +import org.thingsboard.server.common.data.Tenant; | |
42 | 43 | import org.thingsboard.server.common.data.audit.ActionType; |
43 | 44 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
44 | 45 | import org.thingsboard.server.common.data.edge.Edge; |
45 | -import org.thingsboard.server.common.data.edge.EdgeEventType; | |
46 | +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; | |
46 | 47 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
47 | 48 | import org.thingsboard.server.common.data.id.CustomerId; |
48 | 49 | import org.thingsboard.server.common.data.id.DeviceId; |
... | ... | @@ -52,13 +53,14 @@ import org.thingsboard.server.common.data.page.TextPageData; |
52 | 53 | import org.thingsboard.server.common.data.page.TextPageLink; |
53 | 54 | import org.thingsboard.server.common.data.page.TimePageData; |
54 | 55 | import org.thingsboard.server.common.data.page.TimePageLink; |
55 | -import org.thingsboard.server.common.data.rule.RuleChain; | |
56 | 56 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
57 | +import org.thingsboard.server.common.msg.TbMsg; | |
58 | +import org.thingsboard.server.common.msg.TbMsgDataType; | |
59 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | |
57 | 60 | import org.thingsboard.server.dao.device.claim.ClaimResponse; |
58 | 61 | import org.thingsboard.server.dao.device.claim.ClaimResult; |
59 | 62 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
60 | 63 | import org.thingsboard.server.dao.model.ModelConstants; |
61 | -import org.thingsboard.server.gen.transport.TransportProtos; | |
62 | 64 | import org.thingsboard.server.queue.util.TbCoreComponent; |
63 | 65 | import org.thingsboard.server.service.security.model.SecurityUser; |
64 | 66 | import org.thingsboard.server.service.security.permission.Operation; |
... | ... | @@ -79,6 +81,7 @@ public class DeviceController extends BaseController { |
79 | 81 | |
80 | 82 | private static final String DEVICE_ID = "deviceId"; |
81 | 83 | private static final String DEVICE_NAME = "deviceName"; |
84 | + private static final String TENANT_ID = "tenantId"; | |
82 | 85 | |
83 | 86 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
84 | 87 | @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET) |
... | ... | @@ -506,6 +509,56 @@ public class DeviceController extends BaseController { |
506 | 509 | } |
507 | 510 | |
508 | 511 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
512 | + @RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST) | |
513 | + @ResponseBody | |
514 | + public Device assignDeviceToTenant(@PathVariable(TENANT_ID) String strTenantId, | |
515 | + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | |
516 | + checkParameter(TENANT_ID, strTenantId); | |
517 | + checkParameter(DEVICE_ID, strDeviceId); | |
518 | + try { | |
519 | + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | |
520 | + Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT); | |
521 | + | |
522 | + TenantId newTenantId = new TenantId(toUUID(strTenantId)); | |
523 | + Tenant newTenant = tenantService.findTenantById(newTenantId); | |
524 | + if (newTenant == null) { | |
525 | + throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); | |
526 | + } | |
527 | + | |
528 | + Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device); | |
529 | + | |
530 | + logEntityAction(getCurrentUser(), deviceId, assignedDevice, | |
531 | + assignedDevice.getCustomerId(), | |
532 | + ActionType.ASSIGNED_TO_TENANT, null, strTenantId, newTenant.getName()); | |
533 | + | |
534 | + Tenant currentTenant = tenantService.findTenantById(getTenantId()); | |
535 | + pushAssignedFromNotification(currentTenant, newTenantId, assignedDevice); | |
536 | + | |
537 | + return assignedDevice; | |
538 | + } catch (Exception e) { | |
539 | + logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null, | |
540 | + null, | |
541 | + ActionType.ASSIGNED_TO_TENANT, e, strTenantId); | |
542 | + throw handleException(e); | |
543 | + } | |
544 | + } | |
545 | + | |
546 | + private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { | |
547 | + String data = entityToStr(assignedDevice); | |
548 | + if (data != null) { | |
549 | + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); | |
550 | + tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); | |
551 | + } | |
552 | + } | |
553 | + | |
554 | + private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) { | |
555 | + TbMsgMetaData metaData = new TbMsgMetaData(); | |
556 | + metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString()); | |
557 | + metaData.putValue("assignedFromTenantName", tenant.getName()); | |
558 | + return metaData; | |
559 | + } | |
560 | + | |
561 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
509 | 562 | @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST) |
510 | 563 | @ResponseBody |
511 | 564 | public Device assignDeviceToEdge(@PathVariable(EDGE_ID) String strEdgeId, | ... | ... |
... | ... | @@ -197,19 +197,21 @@ public class TelemetryController extends BaseController { |
197 | 197 | @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"}) |
198 | 198 | @ResponseBody |
199 | 199 | public DeferredResult<ResponseEntity> getTimeseries( |
200 | - @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr, | |
200 | + @PathVariable("entityType") String entityType, | |
201 | + @PathVariable("entityId") String entityIdStr, | |
201 | 202 | @RequestParam(name = "keys") String keys, |
202 | 203 | @RequestParam(name = "startTs") Long startTs, |
203 | 204 | @RequestParam(name = "endTs") Long endTs, |
204 | 205 | @RequestParam(name = "interval", defaultValue = "0") Long interval, |
205 | 206 | @RequestParam(name = "limit", defaultValue = "100") Integer limit, |
206 | 207 | @RequestParam(name = "agg", defaultValue = "NONE") String aggStr, |
208 | + @RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy, | |
207 | 209 | @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { |
208 | 210 | return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, |
209 | 211 | (result, tenantId, entityId) -> { |
210 | 212 | // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted |
211 | 213 | Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr); |
212 | - List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg)) | |
214 | + List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy)) | |
213 | 215 | .collect(Collectors.toList()); |
214 | 216 | |
215 | 217 | Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor()); | ... | ... |
... | ... | @@ -146,6 +146,11 @@ public class ThingsboardInstallService { |
146 | 146 | databaseTsUpgradeService.upgradeDatabase("2.5.0"); |
147 | 147 | } |
148 | 148 | |
149 | + case "2.5.4": | |
150 | + log.info("Upgrading ThingsBoard from version 2.5.4 to 2.5.5 ..."); | |
151 | + if (databaseTsUpgradeService != null) { | |
152 | + databaseTsUpgradeService.upgradeDatabase("2.5.4"); | |
153 | + } | |
149 | 154 | |
150 | 155 | log.info("Updating system data..."); |
151 | 156 | ... | ... |
... | ... | @@ -49,6 +49,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase |
49 | 49 | log.info("Schema updated."); |
50 | 50 | break; |
51 | 51 | case "2.5.0": |
52 | + case "2.5.4": | |
52 | 53 | break; |
53 | 54 | default: |
54 | 55 | throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); | ... | ... |
... | ... | @@ -195,6 +195,12 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe |
195 | 195 | executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001"); |
196 | 196 | } |
197 | 197 | break; |
198 | + case "2.5.4": | |
199 | + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | |
200 | + log.info("Load Drop Partitions functions ..."); | |
201 | + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL); | |
202 | + } | |
203 | + break; | |
198 | 204 | default: |
199 | 205 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
200 | 206 | } | ... | ... |
... | ... | @@ -177,6 +177,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr |
177 | 177 | executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001"); |
178 | 178 | } |
179 | 179 | break; |
180 | + case "2.5.4": | |
181 | + break; | |
180 | 182 | default: |
181 | 183 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
182 | 184 | } | ... | ... |
... | ... | @@ -18,7 +18,7 @@ package org.thingsboard.server.service.security.permission; |
18 | 18 | public enum Operation { |
19 | 19 | |
20 | 20 | ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL, |
21 | - READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES, | |
21 | + READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES, ASSIGN_TO_TENANT, | |
22 | 22 | ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE |
23 | 23 | |
24 | 24 | } | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.state; |
17 | 17 | |
18 | -import com.fasterxml.jackson.databind.ObjectMapper; | |
18 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
19 | 19 | import com.google.common.base.Function; |
20 | 20 | import com.google.common.util.concurrent.FutureCallback; |
21 | 21 | import com.google.common.util.concurrent.Futures; |
... | ... | @@ -45,16 +45,17 @@ import org.thingsboard.server.common.data.page.TextPageLink; |
45 | 45 | import org.thingsboard.server.common.msg.TbMsg; |
46 | 46 | import org.thingsboard.server.common.msg.TbMsgDataType; |
47 | 47 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
48 | +import org.thingsboard.server.common.msg.queue.ServiceType; | |
49 | +import org.thingsboard.server.common.msg.queue.TbCallback; | |
50 | +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; | |
48 | 51 | import org.thingsboard.server.dao.attributes.AttributesService; |
49 | 52 | import org.thingsboard.server.dao.device.DeviceService; |
50 | 53 | import org.thingsboard.server.dao.tenant.TenantService; |
51 | 54 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
55 | +import org.thingsboard.server.dao.util.mapping.JacksonUtil; | |
56 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
52 | 57 | import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
53 | 58 | import org.thingsboard.server.queue.discovery.PartitionService; |
54 | -import org.thingsboard.server.common.msg.queue.ServiceType; | |
55 | -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; | |
56 | -import org.thingsboard.server.gen.transport.TransportProtos; | |
57 | -import org.thingsboard.server.common.msg.queue.TbCallback; | |
58 | 59 | import org.thingsboard.server.queue.util.TbCoreComponent; |
59 | 60 | import org.thingsboard.server.service.queue.TbClusterService; |
60 | 61 | import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
... | ... | @@ -90,7 +91,6 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; |
90 | 91 | @Slf4j |
91 | 92 | public class DefaultDeviceStateService implements DeviceStateService { |
92 | 93 | |
93 | - private static final ObjectMapper json = new ObjectMapper(); | |
94 | 94 | public static final String ACTIVITY_STATE = "active"; |
95 | 95 | public static final String LAST_CONNECT_TIME = "lastConnectTime"; |
96 | 96 | public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime"; |
... | ... | @@ -197,15 +197,15 @@ public class DefaultDeviceStateService implements DeviceStateService { |
197 | 197 | if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) { |
198 | 198 | DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); |
199 | 199 | if (stateData != null) { |
200 | - DeviceState state = stateData.getState(); | |
201 | - stateData.getState().setLastActivityTime(lastReportedActivity); | |
202 | - stateData.getMetaData().putValue("scope", SERVER_SCOPE); | |
203 | - pushRuleEngineMessage(stateData, ACTIVITY_EVENT); | |
204 | 200 | save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity); |
205 | 201 | deviceLastSavedActivity.put(deviceId, lastReportedActivity); |
202 | + DeviceState state = stateData.getState(); | |
203 | + state.setLastActivityTime(lastReportedActivity); | |
206 | 204 | if (!state.isActive()) { |
207 | 205 | state.setActive(true); |
208 | 206 | save(deviceId, ACTIVITY_STATE, state.isActive()); |
207 | + stateData.getMetaData().putValue("scope", SERVER_SCOPE); | |
208 | + pushRuleEngineMessage(stateData, ACTIVITY_EVENT); | |
209 | 209 | } |
210 | 210 | } |
211 | 211 | } |
... | ... | @@ -503,8 +503,15 @@ public class DefaultDeviceStateService implements DeviceStateService { |
503 | 503 | private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) { |
504 | 504 | DeviceState state = stateData.getState(); |
505 | 505 | try { |
506 | - TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON | |
507 | - , json.writeValueAsString(state)); | |
506 | + String data; | |
507 | + if (msgType.equals(CONNECT_EVENT)) { | |
508 | + ObjectNode stateNode = JacksonUtil.convertValue(state, ObjectNode.class); | |
509 | + stateNode.remove(ACTIVITY_STATE); | |
510 | + data = JacksonUtil.toString(stateNode); | |
511 | + } else { | |
512 | + data = JacksonUtil.toString(state); | |
513 | + } | |
514 | + TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON, data); | |
508 | 515 | clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null); |
509 | 516 | } catch (Exception e) { |
510 | 517 | log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e); | ... | ... |
... | ... | @@ -154,7 +154,7 @@ public class DefaultTransportApiService implements TransportApiService { |
154 | 154 | return TransportApiResponseMsg.newBuilder() |
155 | 155 | .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); |
156 | 156 | } catch (JsonProcessingException e) { |
157 | - log.warn("[{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e); | |
157 | + log.warn("[{}][{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e); | |
158 | 158 | throw new RuntimeException(e); |
159 | 159 | } finally { |
160 | 160 | deviceCreationLock.unlock(); | ... | ... |
... | ... | @@ -38,19 +38,15 @@ public abstract class AbstractCleanUpService { |
38 | 38 | @Value("${spring.datasource.password}") |
39 | 39 | protected String dbPassword; |
40 | 40 | |
41 | - protected long executeQuery(Connection conn, String query) { | |
42 | - long removed = 0L; | |
43 | - try { | |
44 | - Statement statement = conn.createStatement(); | |
41 | + protected long executeQuery(Connection conn, String query) throws SQLException { | |
42 | + try (Statement statement = conn.createStatement()) { | |
45 | 43 | ResultSet resultSet = statement.executeQuery(query); |
46 | - getWarnings(statement); | |
44 | + if (log.isDebugEnabled()) { | |
45 | + getWarnings(statement); | |
46 | + } | |
47 | 47 | resultSet.next(); |
48 | - removed = resultSet.getLong(1); | |
49 | - log.debug("Successfully executed query: {}", query); | |
50 | - } catch (SQLException e) { | |
51 | - log.debug("Failed to execute query: {} due to: {}", query, e.getMessage()); | |
48 | + return resultSet.getLong(1); | |
52 | 49 | } |
53 | - return removed; | |
54 | 50 | } |
55 | 51 | |
56 | 52 | protected void getWarnings(Statement statement) throws SQLException { |
... | ... | @@ -65,6 +61,6 @@ public abstract class AbstractCleanUpService { |
65 | 61 | } |
66 | 62 | } |
67 | 63 | |
68 | - protected abstract void doCleanUp(Connection connection); | |
64 | + protected abstract void doCleanUp(Connection connection) throws SQLException; | |
69 | 65 | |
70 | 66 | } | ... | ... |
... | ... | @@ -49,7 +49,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { |
49 | 49 | } |
50 | 50 | |
51 | 51 | @Override |
52 | - protected void doCleanUp(Connection connection) { | |
52 | + protected void doCleanUp(Connection connection) throws SQLException { | |
53 | 53 | long totalEdgeEventsRemoved = executeQuery(connection, "call cleanup_edge_events_by_ttl(" + ttl + ", 0);"); |
54 | 54 | log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved); |
55 | 55 | } | ... | ... |
... | ... | @@ -54,7 +54,7 @@ public class EventsCleanUpService extends AbstractCleanUpService { |
54 | 54 | } |
55 | 55 | |
56 | 56 | @Override |
57 | - protected void doCleanUp(Connection connection) { | |
57 | + protected void doCleanUp(Connection connection) throws SQLException { | |
58 | 58 | long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);"); |
59 | 59 | log.info("Total events removed by TTL: [{}]", totalEventsRemoved); |
60 | 60 | } | ... | ... |
... | ... | @@ -22,6 +22,7 @@ import org.thingsboard.server.dao.model.ModelConstants; |
22 | 22 | import org.thingsboard.server.dao.util.PsqlTsDao; |
23 | 23 | |
24 | 24 | import java.sql.Connection; |
25 | +import java.sql.SQLException; | |
25 | 26 | |
26 | 27 | @PsqlTsDao |
27 | 28 | @Service |
... | ... | @@ -32,7 +33,7 @@ public class PsqlTimeseriesCleanUpService extends AbstractTimeseriesCleanUpServi |
32 | 33 | private String partitionType; |
33 | 34 | |
34 | 35 | @Override |
35 | - protected void doCleanUp(Connection connection) { | |
36 | + protected void doCleanUp(Connection connection) throws SQLException { | |
36 | 37 | long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);"); |
37 | 38 | log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved); |
38 | 39 | long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);"); | ... | ... |
... | ... | @@ -21,6 +21,7 @@ import org.thingsboard.server.dao.model.ModelConstants; |
21 | 21 | import org.thingsboard.server.dao.util.TimescaleDBTsDao; |
22 | 22 | |
23 | 23 | import java.sql.Connection; |
24 | +import java.sql.SQLException; | |
24 | 25 | |
25 | 26 | @TimescaleDBTsDao |
26 | 27 | @Service |
... | ... | @@ -28,7 +29,7 @@ import java.sql.Connection; |
28 | 29 | public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService { |
29 | 30 | |
30 | 31 | @Override |
31 | - protected void doCleanUp(Connection connection) { | |
32 | + protected void doCleanUp(Connection connection) throws SQLException { | |
32 | 33 | long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID_STR + "'," + systemTtl + ", 0);"); |
33 | 34 | log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved); |
34 | 35 | } | ... | ... |
... | ... | @@ -30,6 +30,8 @@ |
30 | 30 | |
31 | 31 | <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />--> |
32 | 32 | <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />--> |
33 | +<!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />--> | |
34 | + | |
33 | 35 | |
34 | 36 | <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" /> |
35 | 37 | ... | ... |
... | ... | @@ -589,7 +589,7 @@ transport: |
589 | 589 | edges: |
590 | 590 | rpc: |
591 | 591 | enabled: "${EDGES_RPC_ENABLED:true}" |
592 | - port: "${EDGES_RPC_PORT:60100}" | |
592 | + port: "${EDGES_RPC_PORT:7070}" | |
593 | 593 | ssl: |
594 | 594 | # Enable/disable SSL support |
595 | 595 | enabled: "${EDGES_RPC_SSL_ENABLED:false}" |
... | ... | @@ -619,6 +619,10 @@ swagger: |
619 | 619 | |
620 | 620 | queue: |
621 | 621 | type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) |
622 | + in_memory: | |
623 | + stats: | |
624 | + # For debug lvl | |
625 | + print-interval-ms: "${TB_QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}" | |
622 | 626 | kafka: |
623 | 627 | bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" |
624 | 628 | acks: "${TB_KAFKA_ACKS:all}" |
... | ... | @@ -630,13 +634,21 @@ queue: |
630 | 634 | max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}" |
631 | 635 | max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}" |
632 | 636 | fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}" |
637 | + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" | |
638 | + confluent: | |
639 | + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}" | |
640 | + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" | |
641 | + sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" | |
642 | + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" | |
643 | + other: | |
633 | 644 | topic-properties: |
634 | - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" | |
635 | - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" | |
636 | - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" | |
637 | - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}" | |
638 | - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}" | |
645 | + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" | |
646 | + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" | |
647 | + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" | |
648 | + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" | |
649 | + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" | |
639 | 650 | aws_sqs: |
651 | + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}" | |
640 | 652 | access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" |
641 | 653 | secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}" |
642 | 654 | region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}" | ... | ... |
... | ... | @@ -79,8 +79,14 @@ import java.util.Comparator; |
79 | 79 | import java.util.List; |
80 | 80 | |
81 | 81 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; |
82 | -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; | |
83 | -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; | |
82 | +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; | |
83 | +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; | |
84 | +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | |
85 | +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | |
86 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; | |
87 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | |
88 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; | |
89 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | |
84 | 90 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; |
85 | 91 | |
86 | 92 | @ActiveProfiles("test") |
... | ... | @@ -221,6 +227,7 @@ public abstract class AbstractControllerTest { |
221 | 227 | } |
222 | 228 | |
223 | 229 | private Tenant savedDifferentTenant; |
230 | + | |
224 | 231 | protected void loginDifferentTenant() throws Exception { |
225 | 232 | loginSysAdmin(); |
226 | 233 | Tenant tenant = new Tenant(); |
... | ... | @@ -316,6 +323,10 @@ public abstract class AbstractControllerTest { |
316 | 323 | return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); |
317 | 324 | } |
318 | 325 | |
326 | + protected <T> T doGet(String urlTemplate, Class<T> responseClass, ResultMatcher resultMatcher, Object... urlVariables) throws Exception { | |
327 | + return readResponse(doGet(urlTemplate, urlVariables).andExpect(resultMatcher), responseClass); | |
328 | + } | |
329 | + | |
319 | 330 | protected <T> T doGetAsync(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception { |
320 | 331 | return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); |
321 | 332 | } |
... | ... | @@ -357,9 +368,9 @@ public abstract class AbstractControllerTest { |
357 | 368 | return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); |
358 | 369 | } |
359 | 370 | |
360 | - protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType, | |
361 | - TimePageLink pageLink, | |
362 | - Object... urlVariables) throws Exception { | |
371 | + protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType, | |
372 | + TimePageLink pageLink, | |
373 | + Object... urlVariables) throws Exception { | |
363 | 374 | List<Object> pageLinkVariables = new ArrayList<>(); |
364 | 375 | urlTemplate += "limit={limit}"; |
365 | 376 | pageLinkVariables.add(pageLink.getLimit()); |
... | ... | @@ -425,7 +436,7 @@ public abstract class AbstractControllerTest { |
425 | 436 | return mockMvc.perform(postRequest); |
426 | 437 | } |
427 | 438 | |
428 | - protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception { | |
439 | + protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception { | |
429 | 440 | MockHttpServletRequestBuilder postRequest = post(urlTemplate); |
430 | 441 | setJwtToken(postRequest); |
431 | 442 | String json = json(content); | ... | ... |
... | ... | @@ -15,74 +15,79 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | -import static org.hamcrest.Matchers.containsString; | |
19 | -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; | |
20 | -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | |
21 | - | |
22 | -import java.util.ArrayList; | |
23 | -import java.util.Collections; | |
24 | -import java.util.List; | |
25 | - | |
26 | 18 | import com.datastax.driver.core.utils.UUIDs; |
19 | +import com.fasterxml.jackson.core.type.TypeReference; | |
27 | 20 | import org.apache.commons.lang3.RandomStringUtils; |
28 | -import org.thingsboard.server.common.data.*; | |
21 | +import org.junit.After; | |
22 | +import org.junit.Assert; | |
23 | +import org.junit.Before; | |
24 | +import org.junit.Test; | |
25 | +import org.thingsboard.server.common.data.Customer; | |
26 | +import org.thingsboard.server.common.data.Device; | |
27 | +import org.thingsboard.server.common.data.EntitySubtype; | |
28 | +import org.thingsboard.server.common.data.Tenant; | |
29 | +import org.thingsboard.server.common.data.User; | |
29 | 30 | import org.thingsboard.server.common.data.id.CustomerId; |
30 | 31 | import org.thingsboard.server.common.data.id.DeviceCredentialsId; |
31 | 32 | import org.thingsboard.server.common.data.id.DeviceId; |
32 | 33 | import org.thingsboard.server.common.data.page.TextPageData; |
33 | 34 | import org.thingsboard.server.common.data.page.TextPageLink; |
35 | +import org.thingsboard.server.common.data.relation.EntityRelation; | |
36 | +import org.thingsboard.server.common.data.relation.RelationTypeGroup; | |
34 | 37 | import org.thingsboard.server.common.data.security.Authority; |
35 | 38 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
36 | 39 | import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
37 | 40 | import org.thingsboard.server.dao.model.ModelConstants; |
38 | -import org.junit.After; | |
39 | -import org.junit.Assert; | |
40 | -import org.junit.Before; | |
41 | -import org.junit.Test; | |
42 | 41 | |
43 | -import com.fasterxml.jackson.core.type.TypeReference; | |
42 | +import java.util.ArrayList; | |
43 | +import java.util.Collections; | |
44 | +import java.util.List; | |
45 | + | |
46 | +import static org.hamcrest.Matchers.containsString; | |
47 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | |
48 | +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; | |
44 | 49 | |
45 | 50 | public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
46 | - | |
51 | + | |
47 | 52 | private IdComparator<Device> idComparator = new IdComparator<>(); |
48 | - | |
53 | + | |
49 | 54 | private Tenant savedTenant; |
50 | 55 | private User tenantAdmin; |
51 | - | |
56 | + | |
52 | 57 | @Before |
53 | 58 | public void beforeTest() throws Exception { |
54 | 59 | loginSysAdmin(); |
55 | - | |
60 | + | |
56 | 61 | Tenant tenant = new Tenant(); |
57 | 62 | tenant.setTitle("My tenant"); |
58 | 63 | savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
59 | 64 | Assert.assertNotNull(savedTenant); |
60 | - | |
65 | + | |
61 | 66 | tenantAdmin = new User(); |
62 | 67 | tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
63 | 68 | tenantAdmin.setTenantId(savedTenant.getId()); |
64 | 69 | tenantAdmin.setEmail("tenant2@thingsboard.org"); |
65 | 70 | tenantAdmin.setFirstName("Joe"); |
66 | 71 | tenantAdmin.setLastName("Downs"); |
67 | - | |
72 | + | |
68 | 73 | tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); |
69 | 74 | } |
70 | - | |
75 | + | |
71 | 76 | @After |
72 | 77 | public void afterTest() throws Exception { |
73 | 78 | loginSysAdmin(); |
74 | - | |
75 | - doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) | |
76 | - .andExpect(status().isOk()); | |
79 | + | |
80 | + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) | |
81 | + .andExpect(status().isOk()); | |
77 | 82 | } |
78 | - | |
83 | + | |
79 | 84 | @Test |
80 | 85 | public void testSaveDevice() throws Exception { |
81 | 86 | Device device = new Device(); |
82 | 87 | device.setName("My device"); |
83 | 88 | device.setType("default"); |
84 | 89 | Device savedDevice = doPost("/api/device", device, Device.class); |
85 | - | |
90 | + | |
86 | 91 | Assert.assertNotNull(savedDevice); |
87 | 92 | Assert.assertNotNull(savedDevice.getId()); |
88 | 93 | Assert.assertTrue(savedDevice.getCreatedTime() > 0); |
... | ... | @@ -90,9 +95,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
90 | 95 | Assert.assertNotNull(savedDevice.getCustomerId()); |
91 | 96 | Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId()); |
92 | 97 | Assert.assertEquals(device.getName(), savedDevice.getName()); |
93 | - | |
94 | - DeviceCredentials deviceCredentials = | |
95 | - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); | |
98 | + | |
99 | + DeviceCredentials deviceCredentials = | |
100 | + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); | |
96 | 101 | |
97 | 102 | Assert.assertNotNull(deviceCredentials); |
98 | 103 | Assert.assertNotNull(deviceCredentials.getId()); |
... | ... | @@ -100,10 +105,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
100 | 105 | Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType()); |
101 | 106 | Assert.assertNotNull(deviceCredentials.getCredentialsId()); |
102 | 107 | Assert.assertEquals(20, deviceCredentials.getCredentialsId().length()); |
103 | - | |
108 | + | |
104 | 109 | savedDevice.setName("My new device"); |
105 | 110 | doPost("/api/device", savedDevice, Device.class); |
106 | - | |
111 | + | |
107 | 112 | Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); |
108 | 113 | Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); |
109 | 114 | } |
... | ... | @@ -115,10 +120,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
115 | 120 | device.setType("default"); |
116 | 121 | Device savedDevice = doPost("/api/device", device, Device.class); |
117 | 122 | loginDifferentTenant(); |
118 | - doPost("/api/device", savedDevice, Device.class, status().isForbidden()); | |
123 | + doPost("/api/device", savedDevice, Device.class, status().isNotFound()); | |
119 | 124 | deleteDifferentTenant(); |
120 | 125 | } |
121 | - | |
126 | + | |
122 | 127 | @Test |
123 | 128 | public void testFindDeviceById() throws Exception { |
124 | 129 | Device device = new Device(); |
... | ... | @@ -133,26 +138,27 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
133 | 138 | @Test |
134 | 139 | public void testFindDeviceTypesByTenantId() throws Exception { |
135 | 140 | List<Device> devices = new ArrayList<>(); |
136 | - for (int i=0;i<3;i++) { | |
141 | + for (int i = 0; i < 3; i++) { | |
137 | 142 | Device device = new Device(); |
138 | - device.setName("My device B"+i); | |
143 | + device.setName("My device B" + i); | |
139 | 144 | device.setType("typeB"); |
140 | 145 | devices.add(doPost("/api/device", device, Device.class)); |
141 | 146 | } |
142 | - for (int i=0;i<7;i++) { | |
147 | + for (int i = 0; i < 7; i++) { | |
143 | 148 | Device device = new Device(); |
144 | - device.setName("My device C"+i); | |
149 | + device.setName("My device C" + i); | |
145 | 150 | device.setType("typeC"); |
146 | 151 | devices.add(doPost("/api/device", device, Device.class)); |
147 | 152 | } |
148 | - for (int i=0;i<9;i++) { | |
153 | + for (int i = 0; i < 9; i++) { | |
149 | 154 | Device device = new Device(); |
150 | - device.setName("My device A"+i); | |
155 | + device.setName("My device A" + i); | |
151 | 156 | device.setType("typeA"); |
152 | 157 | devices.add(doPost("/api/device", device, Device.class)); |
153 | 158 | } |
154 | 159 | List<EntitySubtype> deviceTypes = doGetTyped("/api/device/types", |
155 | - new TypeReference<List<EntitySubtype>>(){}); | |
160 | + new TypeReference<List<EntitySubtype>>() { | |
161 | + }); | |
156 | 162 | |
157 | 163 | Assert.assertNotNull(deviceTypes); |
158 | 164 | Assert.assertEquals(3, deviceTypes.size()); |
... | ... | @@ -160,19 +166,19 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
160 | 166 | Assert.assertEquals("typeB", deviceTypes.get(1).getType()); |
161 | 167 | Assert.assertEquals("typeC", deviceTypes.get(2).getType()); |
162 | 168 | } |
163 | - | |
169 | + | |
164 | 170 | @Test |
165 | 171 | public void testDeleteDevice() throws Exception { |
166 | 172 | Device device = new Device(); |
167 | 173 | device.setName("My device"); |
168 | 174 | device.setType("default"); |
169 | 175 | Device savedDevice = doPost("/api/device", device, Device.class); |
170 | - | |
171 | - doDelete("/api/device/"+savedDevice.getId().getId().toString()) | |
172 | - .andExpect(status().isOk()); | |
173 | 176 | |
174 | - doGet("/api/device/"+savedDevice.getId().getId().toString()) | |
175 | - .andExpect(status().isNotFound()); | |
177 | + doDelete("/api/device/" + savedDevice.getId().getId().toString()) | |
178 | + .andExpect(status().isOk()); | |
179 | + | |
180 | + doGet("/api/device/" + savedDevice.getId().getId().toString()) | |
181 | + .andExpect(status().isNotFound()); | |
176 | 182 | } |
177 | 183 | |
178 | 184 | @Test |
... | ... | @@ -189,52 +195,52 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
189 | 195 | Device device = new Device(); |
190 | 196 | device.setType("default"); |
191 | 197 | doPost("/api/device", device) |
192 | - .andExpect(status().isBadRequest()) | |
193 | - .andExpect(statusReason(containsString("Device name should be specified"))); | |
198 | + .andExpect(status().isBadRequest()) | |
199 | + .andExpect(statusReason(containsString("Device name should be specified"))); | |
194 | 200 | } |
195 | - | |
201 | + | |
196 | 202 | @Test |
197 | 203 | public void testAssignUnassignDeviceToCustomer() throws Exception { |
198 | 204 | Device device = new Device(); |
199 | 205 | device.setName("My device"); |
200 | 206 | device.setType("default"); |
201 | 207 | Device savedDevice = doPost("/api/device", device, Device.class); |
202 | - | |
208 | + | |
203 | 209 | Customer customer = new Customer(); |
204 | 210 | customer.setTitle("My customer"); |
205 | 211 | Customer savedCustomer = doPost("/api/customer", customer, Customer.class); |
206 | - | |
207 | - Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString() | |
212 | + | |
213 | + Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString() | |
208 | 214 | + "/device/" + savedDevice.getId().getId().toString(), Device.class); |
209 | 215 | Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId()); |
210 | - | |
216 | + | |
211 | 217 | Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); |
212 | 218 | Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId()); |
213 | 219 | |
214 | - Device unassignedDevice = | |
220 | + Device unassignedDevice = | |
215 | 221 | doDelete("/api/customer/device/" + savedDevice.getId().getId().toString(), Device.class); |
216 | 222 | Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); |
217 | - | |
223 | + | |
218 | 224 | foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); |
219 | 225 | Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId()); |
220 | 226 | } |
221 | - | |
227 | + | |
222 | 228 | @Test |
223 | 229 | public void testAssignDeviceToNonExistentCustomer() throws Exception { |
224 | 230 | Device device = new Device(); |
225 | 231 | device.setName("My device"); |
226 | 232 | device.setType("default"); |
227 | 233 | Device savedDevice = doPost("/api/device", device, Device.class); |
228 | - | |
234 | + | |
229 | 235 | doPost("/api/customer/" + UUIDs.timeBased().toString() |
230 | 236 | + "/device/" + savedDevice.getId().getId().toString()) |
231 | - .andExpect(status().isNotFound()); | |
237 | + .andExpect(status().isNotFound()); | |
232 | 238 | } |
233 | - | |
239 | + | |
234 | 240 | @Test |
235 | 241 | public void testAssignDeviceToCustomerFromDifferentTenant() throws Exception { |
236 | 242 | loginSysAdmin(); |
237 | - | |
243 | + | |
238 | 244 | Tenant tenant2 = new Tenant(); |
239 | 245 | tenant2.setTitle("Different tenant"); |
240 | 246 | Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class); |
... | ... | @@ -246,103 +252,103 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
246 | 252 | tenantAdmin2.setEmail("tenant3@thingsboard.org"); |
247 | 253 | tenantAdmin2.setFirstName("Joe"); |
248 | 254 | tenantAdmin2.setLastName("Downs"); |
249 | - | |
255 | + | |
250 | 256 | tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); |
251 | - | |
257 | + | |
252 | 258 | Customer customer = new Customer(); |
253 | 259 | customer.setTitle("Different customer"); |
254 | 260 | Customer savedCustomer = doPost("/api/customer", customer, Customer.class); |
255 | 261 | |
256 | 262 | login(tenantAdmin.getEmail(), "testPassword1"); |
257 | - | |
263 | + | |
258 | 264 | Device device = new Device(); |
259 | 265 | device.setName("My device"); |
260 | 266 | device.setType("default"); |
261 | 267 | Device savedDevice = doPost("/api/device", device, Device.class); |
262 | - | |
268 | + | |
263 | 269 | doPost("/api/customer/" + savedCustomer.getId().getId().toString() |
264 | 270 | + "/device/" + savedDevice.getId().getId().toString()) |
265 | - .andExpect(status().isForbidden()); | |
266 | - | |
271 | + .andExpect(status().isForbidden()); | |
272 | + | |
267 | 273 | loginSysAdmin(); |
268 | - | |
269 | - doDelete("/api/tenant/"+savedTenant2.getId().getId().toString()) | |
270 | - .andExpect(status().isOk()); | |
274 | + | |
275 | + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) | |
276 | + .andExpect(status().isOk()); | |
271 | 277 | } |
272 | - | |
278 | + | |
273 | 279 | @Test |
274 | 280 | public void testFindDeviceCredentialsByDeviceId() throws Exception { |
275 | 281 | Device device = new Device(); |
276 | 282 | device.setName("My device"); |
277 | 283 | device.setType("default"); |
278 | 284 | Device savedDevice = doPost("/api/device", device, Device.class); |
279 | - DeviceCredentials deviceCredentials = | |
280 | - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); | |
285 | + DeviceCredentials deviceCredentials = | |
286 | + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); | |
281 | 287 | Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); |
282 | 288 | } |
283 | - | |
289 | + | |
284 | 290 | @Test |
285 | 291 | public void testSaveDeviceCredentials() throws Exception { |
286 | 292 | Device device = new Device(); |
287 | 293 | device.setName("My device"); |
288 | 294 | device.setType("default"); |
289 | 295 | Device savedDevice = doPost("/api/device", device, Device.class); |
290 | - DeviceCredentials deviceCredentials = | |
291 | - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); | |
296 | + DeviceCredentials deviceCredentials = | |
297 | + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); | |
292 | 298 | Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); |
293 | 299 | deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); |
294 | 300 | deviceCredentials.setCredentialsId("access_token"); |
295 | 301 | doPost("/api/device/credentials", deviceCredentials) |
296 | - .andExpect(status().isOk()); | |
297 | - | |
298 | - DeviceCredentials foundDeviceCredentials = | |
302 | + .andExpect(status().isOk()); | |
303 | + | |
304 | + DeviceCredentials foundDeviceCredentials = | |
299 | 305 | doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); |
300 | - | |
306 | + | |
301 | 307 | Assert.assertEquals(deviceCredentials, foundDeviceCredentials); |
302 | 308 | } |
303 | - | |
309 | + | |
304 | 310 | @Test |
305 | 311 | public void testSaveDeviceCredentialsWithEmptyDevice() throws Exception { |
306 | 312 | DeviceCredentials deviceCredentials = new DeviceCredentials(); |
307 | 313 | doPost("/api/device/credentials", deviceCredentials) |
308 | - .andExpect(status().isBadRequest()); | |
314 | + .andExpect(status().isBadRequest()); | |
309 | 315 | } |
310 | - | |
316 | + | |
311 | 317 | @Test |
312 | 318 | public void testSaveDeviceCredentialsWithEmptyCredentialsType() throws Exception { |
313 | 319 | Device device = new Device(); |
314 | 320 | device.setName("My device"); |
315 | 321 | device.setType("default"); |
316 | 322 | Device savedDevice = doPost("/api/device", device, Device.class); |
317 | - DeviceCredentials deviceCredentials = | |
323 | + DeviceCredentials deviceCredentials = | |
318 | 324 | doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); |
319 | 325 | deviceCredentials.setCredentialsType(null); |
320 | 326 | doPost("/api/device/credentials", deviceCredentials) |
321 | - .andExpect(status().isBadRequest()) | |
322 | - .andExpect(statusReason(containsString("Device credentials type should be specified"))); | |
327 | + .andExpect(status().isBadRequest()) | |
328 | + .andExpect(statusReason(containsString("Device credentials type should be specified"))); | |
323 | 329 | } |
324 | - | |
330 | + | |
325 | 331 | @Test |
326 | 332 | public void testSaveDeviceCredentialsWithEmptyCredentialsId() throws Exception { |
327 | 333 | Device device = new Device(); |
328 | 334 | device.setName("My device"); |
329 | 335 | device.setType("default"); |
330 | 336 | Device savedDevice = doPost("/api/device", device, Device.class); |
331 | - DeviceCredentials deviceCredentials = | |
337 | + DeviceCredentials deviceCredentials = | |
332 | 338 | doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); |
333 | 339 | deviceCredentials.setCredentialsId(null); |
334 | 340 | doPost("/api/device/credentials", deviceCredentials) |
335 | - .andExpect(status().isBadRequest()) | |
336 | - .andExpect(statusReason(containsString("Device credentials id should be specified"))); | |
341 | + .andExpect(status().isBadRequest()) | |
342 | + .andExpect(statusReason(containsString("Device credentials id should be specified"))); | |
337 | 343 | } |
338 | - | |
344 | + | |
339 | 345 | @Test |
340 | 346 | public void testSaveNonExistentDeviceCredentials() throws Exception { |
341 | 347 | Device device = new Device(); |
342 | 348 | device.setName("My device"); |
343 | 349 | device.setType("default"); |
344 | 350 | Device savedDevice = doPost("/api/device", device, Device.class); |
345 | - DeviceCredentials deviceCredentials = | |
351 | + DeviceCredentials deviceCredentials = | |
346 | 352 | doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); |
347 | 353 | DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(UUIDs.timeBased())); |
348 | 354 | newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime()); |
... | ... | @@ -350,29 +356,29 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
350 | 356 | newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType()); |
351 | 357 | newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId()); |
352 | 358 | doPost("/api/device/credentials", newDeviceCredentials) |
353 | - .andExpect(status().isBadRequest()) | |
354 | - .andExpect(statusReason(containsString("Unable to update non-existent device credentials"))); | |
359 | + .andExpect(status().isBadRequest()) | |
360 | + .andExpect(statusReason(containsString("Unable to update non-existent device credentials"))); | |
355 | 361 | } |
356 | - | |
362 | + | |
357 | 363 | @Test |
358 | 364 | public void testSaveDeviceCredentialsWithNonExistentDevice() throws Exception { |
359 | 365 | Device device = new Device(); |
360 | 366 | device.setName("My device"); |
361 | 367 | device.setType("default"); |
362 | 368 | Device savedDevice = doPost("/api/device", device, Device.class); |
363 | - DeviceCredentials deviceCredentials = | |
369 | + DeviceCredentials deviceCredentials = | |
364 | 370 | doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); |
365 | 371 | deviceCredentials.setDeviceId(new DeviceId(UUIDs.timeBased())); |
366 | 372 | doPost("/api/device/credentials", deviceCredentials) |
367 | - .andExpect(status().isNotFound()); | |
373 | + .andExpect(status().isNotFound()); | |
368 | 374 | } |
369 | 375 | |
370 | 376 | @Test |
371 | 377 | public void testFindTenantDevices() throws Exception { |
372 | 378 | List<Device> devices = new ArrayList<>(); |
373 | - for (int i=0;i<178;i++) { | |
379 | + for (int i = 0; i < 178; i++) { | |
374 | 380 | Device device = new Device(); |
375 | - device.setName("Device"+i); | |
381 | + device.setName("Device" + i); | |
376 | 382 | device.setType("default"); |
377 | 383 | devices.add(doPost("/api/device", device, Device.class)); |
378 | 384 | } |
... | ... | @@ -380,28 +386,29 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
380 | 386 | TextPageLink pageLink = new TextPageLink(23); |
381 | 387 | TextPageData<Device> pageData = null; |
382 | 388 | do { |
383 | - pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
384 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
389 | + pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
390 | + new TypeReference<TextPageData<Device>>() { | |
391 | + }, pageLink); | |
385 | 392 | loadedDevices.addAll(pageData.getData()); |
386 | 393 | if (pageData.hasNext()) { |
387 | 394 | pageLink = pageData.getNextPageLink(); |
388 | 395 | } |
389 | 396 | } while (pageData.hasNext()); |
390 | - | |
397 | + | |
391 | 398 | Collections.sort(devices, idComparator); |
392 | 399 | Collections.sort(loadedDevices, idComparator); |
393 | - | |
400 | + | |
394 | 401 | Assert.assertEquals(devices, loadedDevices); |
395 | 402 | } |
396 | - | |
403 | + | |
397 | 404 | @Test |
398 | 405 | public void testFindTenantDevicesByName() throws Exception { |
399 | 406 | String title1 = "Device title 1"; |
400 | 407 | List<Device> devicesTitle1 = new ArrayList<>(); |
401 | - for (int i=0;i<143;i++) { | |
408 | + for (int i = 0; i < 143; i++) { | |
402 | 409 | Device device = new Device(); |
403 | 410 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
404 | - String name = title1+suffix; | |
411 | + String name = title1 + suffix; | |
405 | 412 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
406 | 413 | device.setName(name); |
407 | 414 | device.setType("default"); |
... | ... | @@ -409,38 +416,40 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
409 | 416 | } |
410 | 417 | String title2 = "Device title 2"; |
411 | 418 | List<Device> devicesTitle2 = new ArrayList<>(); |
412 | - for (int i=0;i<75;i++) { | |
419 | + for (int i = 0; i < 75; i++) { | |
413 | 420 | Device device = new Device(); |
414 | 421 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
415 | - String name = title2+suffix; | |
422 | + String name = title2 + suffix; | |
416 | 423 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
417 | 424 | device.setName(name); |
418 | 425 | device.setType("default"); |
419 | 426 | devicesTitle2.add(doPost("/api/device", device, Device.class)); |
420 | 427 | } |
421 | - | |
428 | + | |
422 | 429 | List<Device> loadedDevicesTitle1 = new ArrayList<>(); |
423 | 430 | TextPageLink pageLink = new TextPageLink(15, title1); |
424 | 431 | TextPageData<Device> pageData = null; |
425 | 432 | do { |
426 | - pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
427 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
433 | + pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
434 | + new TypeReference<TextPageData<Device>>() { | |
435 | + }, pageLink); | |
428 | 436 | loadedDevicesTitle1.addAll(pageData.getData()); |
429 | 437 | if (pageData.hasNext()) { |
430 | 438 | pageLink = pageData.getNextPageLink(); |
431 | 439 | } |
432 | 440 | } while (pageData.hasNext()); |
433 | - | |
441 | + | |
434 | 442 | Collections.sort(devicesTitle1, idComparator); |
435 | 443 | Collections.sort(loadedDevicesTitle1, idComparator); |
436 | - | |
444 | + | |
437 | 445 | Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); |
438 | - | |
446 | + | |
439 | 447 | List<Device> loadedDevicesTitle2 = new ArrayList<>(); |
440 | 448 | pageLink = new TextPageLink(4, title2); |
441 | 449 | do { |
442 | - pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
443 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
450 | + pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
451 | + new TypeReference<TextPageData<Device>>() { | |
452 | + }, pageLink); | |
444 | 453 | loadedDevicesTitle2.addAll(pageData.getData()); |
445 | 454 | if (pageData.hasNext()) { |
446 | 455 | pageLink = pageData.getNextPageLink(); |
... | ... | @@ -449,28 +458,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
449 | 458 | |
450 | 459 | Collections.sort(devicesTitle2, idComparator); |
451 | 460 | Collections.sort(loadedDevicesTitle2, idComparator); |
452 | - | |
461 | + | |
453 | 462 | Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); |
454 | - | |
463 | + | |
455 | 464 | for (Device device : loadedDevicesTitle1) { |
456 | - doDelete("/api/device/"+device.getId().getId().toString()) | |
457 | - .andExpect(status().isOk()); | |
465 | + doDelete("/api/device/" + device.getId().getId().toString()) | |
466 | + .andExpect(status().isOk()); | |
458 | 467 | } |
459 | - | |
468 | + | |
460 | 469 | pageLink = new TextPageLink(4, title1); |
461 | - pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
462 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
470 | + pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
471 | + new TypeReference<TextPageData<Device>>() { | |
472 | + }, pageLink); | |
463 | 473 | Assert.assertFalse(pageData.hasNext()); |
464 | 474 | Assert.assertEquals(0, pageData.getData().size()); |
465 | - | |
475 | + | |
466 | 476 | for (Device device : loadedDevicesTitle2) { |
467 | - doDelete("/api/device/"+device.getId().getId().toString()) | |
468 | - .andExpect(status().isOk()); | |
477 | + doDelete("/api/device/" + device.getId().getId().toString()) | |
478 | + .andExpect(status().isOk()); | |
469 | 479 | } |
470 | - | |
480 | + | |
471 | 481 | pageLink = new TextPageLink(4, title2); |
472 | - pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
473 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
482 | + pageData = doGetTypedWithPageLink("/api/tenant/devices?", | |
483 | + new TypeReference<TextPageData<Device>>() { | |
484 | + }, pageLink); | |
474 | 485 | Assert.assertFalse(pageData.hasNext()); |
475 | 486 | Assert.assertEquals(0, pageData.getData().size()); |
476 | 487 | } |
... | ... | @@ -480,10 +491,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
480 | 491 | String title1 = "Device title 1"; |
481 | 492 | String type1 = "typeA"; |
482 | 493 | List<Device> devicesType1 = new ArrayList<>(); |
483 | - for (int i=0;i<143;i++) { | |
494 | + for (int i = 0; i < 143; i++) { | |
484 | 495 | Device device = new Device(); |
485 | 496 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
486 | - String name = title1+suffix; | |
497 | + String name = title1 + suffix; | |
487 | 498 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
488 | 499 | device.setName(name); |
489 | 500 | device.setType(type1); |
... | ... | @@ -492,10 +503,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
492 | 503 | String title2 = "Device title 2"; |
493 | 504 | String type2 = "typeB"; |
494 | 505 | List<Device> devicesType2 = new ArrayList<>(); |
495 | - for (int i=0;i<75;i++) { | |
506 | + for (int i = 0; i < 75; i++) { | |
496 | 507 | Device device = new Device(); |
497 | 508 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
498 | - String name = title2+suffix; | |
509 | + String name = title2 + suffix; | |
499 | 510 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
500 | 511 | device.setName(name); |
501 | 512 | device.setType(type2); |
... | ... | @@ -507,7 +518,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
507 | 518 | TextPageData<Device> pageData = null; |
508 | 519 | do { |
509 | 520 | pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", |
510 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); | |
521 | + new TypeReference<TextPageData<Device>>() { | |
522 | + }, pageLink, type1); | |
511 | 523 | loadedDevicesType1.addAll(pageData.getData()); |
512 | 524 | if (pageData.hasNext()) { |
513 | 525 | pageLink = pageData.getNextPageLink(); |
... | ... | @@ -523,7 +535,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
523 | 535 | pageLink = new TextPageLink(4); |
524 | 536 | do { |
525 | 537 | pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", |
526 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); | |
538 | + new TypeReference<TextPageData<Device>>() { | |
539 | + }, pageLink, type2); | |
527 | 540 | loadedDevicesType2.addAll(pageData.getData()); |
528 | 541 | if (pageData.hasNext()) { |
529 | 542 | pageLink = pageData.getNextPageLink(); |
... | ... | @@ -536,63 +549,66 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
536 | 549 | Assert.assertEquals(devicesType2, loadedDevicesType2); |
537 | 550 | |
538 | 551 | for (Device device : loadedDevicesType1) { |
539 | - doDelete("/api/device/"+device.getId().getId().toString()) | |
552 | + doDelete("/api/device/" + device.getId().getId().toString()) | |
540 | 553 | .andExpect(status().isOk()); |
541 | 554 | } |
542 | 555 | |
543 | 556 | pageLink = new TextPageLink(4); |
544 | 557 | pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", |
545 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); | |
558 | + new TypeReference<TextPageData<Device>>() { | |
559 | + }, pageLink, type1); | |
546 | 560 | Assert.assertFalse(pageData.hasNext()); |
547 | 561 | Assert.assertEquals(0, pageData.getData().size()); |
548 | 562 | |
549 | 563 | for (Device device : loadedDevicesType2) { |
550 | - doDelete("/api/device/"+device.getId().getId().toString()) | |
564 | + doDelete("/api/device/" + device.getId().getId().toString()) | |
551 | 565 | .andExpect(status().isOk()); |
552 | 566 | } |
553 | 567 | |
554 | 568 | pageLink = new TextPageLink(4); |
555 | 569 | pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", |
556 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); | |
570 | + new TypeReference<TextPageData<Device>>() { | |
571 | + }, pageLink, type2); | |
557 | 572 | Assert.assertFalse(pageData.hasNext()); |
558 | 573 | Assert.assertEquals(0, pageData.getData().size()); |
559 | 574 | } |
560 | - | |
575 | + | |
561 | 576 | @Test |
562 | 577 | public void testFindCustomerDevices() throws Exception { |
563 | 578 | Customer customer = new Customer(); |
564 | 579 | customer.setTitle("Test customer"); |
565 | 580 | customer = doPost("/api/customer", customer, Customer.class); |
566 | 581 | CustomerId customerId = customer.getId(); |
567 | - | |
582 | + | |
568 | 583 | List<Device> devices = new ArrayList<>(); |
569 | - for (int i=0;i<128;i++) { | |
584 | + for (int i = 0; i < 128; i++) { | |
570 | 585 | Device device = new Device(); |
571 | - device.setName("Device"+i); | |
586 | + device.setName("Device" + i); | |
572 | 587 | device.setType("default"); |
573 | 588 | device = doPost("/api/device", device, Device.class); |
574 | - devices.add(doPost("/api/customer/" + customerId.getId().toString() | |
575 | - + "/device/" + device.getId().getId().toString(), Device.class)); | |
589 | + devices.add(doPost("/api/customer/" + customerId.getId().toString() | |
590 | + + "/device/" + device.getId().getId().toString(), Device.class)); | |
576 | 591 | } |
577 | - | |
592 | + | |
578 | 593 | List<Device> loadedDevices = new ArrayList<>(); |
579 | 594 | TextPageLink pageLink = new TextPageLink(23); |
580 | 595 | TextPageData<Device> pageData = null; |
581 | 596 | do { |
582 | - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
583 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
597 | + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
598 | + new TypeReference<TextPageData<Device>>() { | |
599 | + }, pageLink); | |
584 | 600 | loadedDevices.addAll(pageData.getData()); |
585 | 601 | if (pageData.hasNext()) { |
586 | 602 | pageLink = pageData.getNextPageLink(); |
587 | 603 | } |
588 | 604 | } while (pageData.hasNext()); |
589 | - | |
605 | + | |
590 | 606 | Collections.sort(devices, idComparator); |
591 | 607 | Collections.sort(loadedDevices, idComparator); |
592 | - | |
608 | + | |
593 | 609 | Assert.assertEquals(devices, loadedDevices); |
594 | 610 | } |
595 | - | |
611 | + | |
596 | 612 | @Test |
597 | 613 | public void testFindCustomerDevicesByName() throws Exception { |
598 | 614 | Customer customer = new Customer(); |
... | ... | @@ -602,53 +618,55 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
602 | 618 | |
603 | 619 | String title1 = "Device title 1"; |
604 | 620 | List<Device> devicesTitle1 = new ArrayList<>(); |
605 | - for (int i=0;i<125;i++) { | |
621 | + for (int i = 0; i < 125; i++) { | |
606 | 622 | Device device = new Device(); |
607 | 623 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
608 | - String name = title1+suffix; | |
624 | + String name = title1 + suffix; | |
609 | 625 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
610 | 626 | device.setName(name); |
611 | 627 | device.setType("default"); |
612 | 628 | device = doPost("/api/device", device, Device.class); |
613 | - devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString() | |
629 | + devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString() | |
614 | 630 | + "/device/" + device.getId().getId().toString(), Device.class)); |
615 | 631 | } |
616 | 632 | String title2 = "Device title 2"; |
617 | 633 | List<Device> devicesTitle2 = new ArrayList<>(); |
618 | - for (int i=0;i<143;i++) { | |
634 | + for (int i = 0; i < 143; i++) { | |
619 | 635 | Device device = new Device(); |
620 | 636 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
621 | - String name = title2+suffix; | |
637 | + String name = title2 + suffix; | |
622 | 638 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
623 | 639 | device.setName(name); |
624 | 640 | device.setType("default"); |
625 | 641 | device = doPost("/api/device", device, Device.class); |
626 | - devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString() | |
642 | + devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString() | |
627 | 643 | + "/device/" + device.getId().getId().toString(), Device.class)); |
628 | 644 | } |
629 | - | |
645 | + | |
630 | 646 | List<Device> loadedDevicesTitle1 = new ArrayList<>(); |
631 | 647 | TextPageLink pageLink = new TextPageLink(15, title1); |
632 | 648 | TextPageData<Device> pageData = null; |
633 | 649 | do { |
634 | - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
635 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
650 | + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
651 | + new TypeReference<TextPageData<Device>>() { | |
652 | + }, pageLink); | |
636 | 653 | loadedDevicesTitle1.addAll(pageData.getData()); |
637 | 654 | if (pageData.hasNext()) { |
638 | 655 | pageLink = pageData.getNextPageLink(); |
639 | 656 | } |
640 | 657 | } while (pageData.hasNext()); |
641 | - | |
658 | + | |
642 | 659 | Collections.sort(devicesTitle1, idComparator); |
643 | 660 | Collections.sort(loadedDevicesTitle1, idComparator); |
644 | - | |
661 | + | |
645 | 662 | Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); |
646 | - | |
663 | + | |
647 | 664 | List<Device> loadedDevicesTitle2 = new ArrayList<>(); |
648 | 665 | pageLink = new TextPageLink(4, title2); |
649 | 666 | do { |
650 | - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
651 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
667 | + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
668 | + new TypeReference<TextPageData<Device>>() { | |
669 | + }, pageLink); | |
652 | 670 | loadedDevicesTitle2.addAll(pageData.getData()); |
653 | 671 | if (pageData.hasNext()) { |
654 | 672 | pageLink = pageData.getNextPageLink(); |
... | ... | @@ -657,28 +675,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
657 | 675 | |
658 | 676 | Collections.sort(devicesTitle2, idComparator); |
659 | 677 | Collections.sort(loadedDevicesTitle2, idComparator); |
660 | - | |
678 | + | |
661 | 679 | Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); |
662 | - | |
680 | + | |
663 | 681 | for (Device device : loadedDevicesTitle1) { |
664 | 682 | doDelete("/api/customer/device/" + device.getId().getId().toString()) |
665 | - .andExpect(status().isOk()); | |
683 | + .andExpect(status().isOk()); | |
666 | 684 | } |
667 | - | |
685 | + | |
668 | 686 | pageLink = new TextPageLink(4, title1); |
669 | - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
670 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
687 | + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
688 | + new TypeReference<TextPageData<Device>>() { | |
689 | + }, pageLink); | |
671 | 690 | Assert.assertFalse(pageData.hasNext()); |
672 | 691 | Assert.assertEquals(0, pageData.getData().size()); |
673 | - | |
692 | + | |
674 | 693 | for (Device device : loadedDevicesTitle2) { |
675 | 694 | doDelete("/api/customer/device/" + device.getId().getId().toString()) |
676 | - .andExpect(status().isOk()); | |
695 | + .andExpect(status().isOk()); | |
677 | 696 | } |
678 | - | |
697 | + | |
679 | 698 | pageLink = new TextPageLink(4, title2); |
680 | - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
681 | - new TypeReference<TextPageData<Device>>(){}, pageLink); | |
699 | + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", | |
700 | + new TypeReference<TextPageData<Device>>() { | |
701 | + }, pageLink); | |
682 | 702 | Assert.assertFalse(pageData.hasNext()); |
683 | 703 | Assert.assertEquals(0, pageData.getData().size()); |
684 | 704 | } |
... | ... | @@ -693,10 +713,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
693 | 713 | String title1 = "Device title 1"; |
694 | 714 | String type1 = "typeC"; |
695 | 715 | List<Device> devicesType1 = new ArrayList<>(); |
696 | - for (int i=0;i<125;i++) { | |
716 | + for (int i = 0; i < 125; i++) { | |
697 | 717 | Device device = new Device(); |
698 | 718 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
699 | - String name = title1+suffix; | |
719 | + String name = title1 + suffix; | |
700 | 720 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
701 | 721 | device.setName(name); |
702 | 722 | device.setType(type1); |
... | ... | @@ -707,10 +727,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
707 | 727 | String title2 = "Device title 2"; |
708 | 728 | String type2 = "typeD"; |
709 | 729 | List<Device> devicesType2 = new ArrayList<>(); |
710 | - for (int i=0;i<143;i++) { | |
730 | + for (int i = 0; i < 143; i++) { | |
711 | 731 | Device device = new Device(); |
712 | 732 | String suffix = RandomStringUtils.randomAlphanumeric(15); |
713 | - String name = title2+suffix; | |
733 | + String name = title2 + suffix; | |
714 | 734 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
715 | 735 | device.setName(name); |
716 | 736 | device.setType(type2); |
... | ... | @@ -724,7 +744,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
724 | 744 | TextPageData<Device> pageData = null; |
725 | 745 | do { |
726 | 746 | pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", |
727 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); | |
747 | + new TypeReference<TextPageData<Device>>() { | |
748 | + }, pageLink, type1); | |
728 | 749 | loadedDevicesType1.addAll(pageData.getData()); |
729 | 750 | if (pageData.hasNext()) { |
730 | 751 | pageLink = pageData.getNextPageLink(); |
... | ... | @@ -740,7 +761,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
740 | 761 | pageLink = new TextPageLink(4); |
741 | 762 | do { |
742 | 763 | pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", |
743 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); | |
764 | + new TypeReference<TextPageData<Device>>() { | |
765 | + }, pageLink, type2); | |
744 | 766 | loadedDevicesType2.addAll(pageData.getData()); |
745 | 767 | if (pageData.hasNext()) { |
746 | 768 | pageLink = pageData.getNextPageLink(); |
... | ... | @@ -759,7 +781,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
759 | 781 | |
760 | 782 | pageLink = new TextPageLink(4); |
761 | 783 | pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", |
762 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); | |
784 | + new TypeReference<TextPageData<Device>>() { | |
785 | + }, pageLink, type1); | |
763 | 786 | Assert.assertFalse(pageData.hasNext()); |
764 | 787 | Assert.assertEquals(0, pageData.getData().size()); |
765 | 788 | |
... | ... | @@ -770,9 +793,60 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { |
770 | 793 | |
771 | 794 | pageLink = new TextPageLink(4); |
772 | 795 | pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", |
773 | - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); | |
796 | + new TypeReference<TextPageData<Device>>() { | |
797 | + }, pageLink, type2); | |
774 | 798 | Assert.assertFalse(pageData.hasNext()); |
775 | 799 | Assert.assertEquals(0, pageData.getData().size()); |
776 | 800 | } |
777 | 801 | |
802 | + @Test | |
803 | + public void testAssignDeviceToTenant() throws Exception { | |
804 | + Device device = new Device(); | |
805 | + device.setName("My device"); | |
806 | + device.setType("default"); | |
807 | + Device savedDevice = doPost("/api/device", device, Device.class); | |
808 | + | |
809 | + Device anotherDevice = new Device(); | |
810 | + anotherDevice.setName("My device1"); | |
811 | + anotherDevice.setType("default"); | |
812 | + Device savedAnotherDevice = doPost("/api/device", anotherDevice, Device.class); | |
813 | + | |
814 | + EntityRelation relation = new EntityRelation(); | |
815 | + relation.setFrom(savedDevice.getId()); | |
816 | + relation.setTo(savedAnotherDevice.getId()); | |
817 | + relation.setTypeGroup(RelationTypeGroup.COMMON); | |
818 | + relation.setType("Contains"); | |
819 | + doPost("/api/relation", relation).andExpect(status().isOk()); | |
820 | + | |
821 | + loginSysAdmin(); | |
822 | + Tenant tenant = new Tenant(); | |
823 | + tenant.setTitle("Different tenant"); | |
824 | + Tenant savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class); | |
825 | + Assert.assertNotNull(savedDifferentTenant); | |
826 | + | |
827 | + User user = new User(); | |
828 | + user.setAuthority(Authority.TENANT_ADMIN); | |
829 | + user.setTenantId(savedDifferentTenant.getId()); | |
830 | + user.setEmail("tenant9@thingsboard.org"); | |
831 | + user.setFirstName("Sam"); | |
832 | + user.setLastName("Downs"); | |
833 | + | |
834 | + createUserAndLogin(user, "testPassword1"); | |
835 | + | |
836 | + login("tenant2@thingsboard.org", "testPassword1"); | |
837 | + Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class); | |
838 | + | |
839 | + doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class, status().isNotFound()); | |
840 | + | |
841 | + login("tenant9@thingsboard.org", "testPassword1"); | |
842 | + | |
843 | + Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class); | |
844 | + Assert.assertNotNull(foundDevice1); | |
845 | + | |
846 | + doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound()); | |
847 | + | |
848 | + loginSysAdmin(); | |
849 | + doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) | |
850 | + .andExpect(status().isOk()); | |
851 | + } | |
778 | 852 | } | ... | ... |
... | ... | @@ -93,7 +93,7 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest { |
93 | 93 | |
94 | 94 | doPost("/api/relation", relation); |
95 | 95 | |
96 | - Thread.sleep(1000); | |
96 | + Thread.sleep(2000); | |
97 | 97 | |
98 | 98 | List<EdgeEvent> edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?", |
99 | 99 | new TypeReference<TimePageData<EdgeEvent>>() { | ... | ... |
... | ... | @@ -20,7 +20,7 @@ |
20 | 20 | <modelVersion>4.0.0</modelVersion> |
21 | 21 | <parent> |
22 | 22 | <groupId>org.thingsboard</groupId> |
23 | - <version>2.5.3-SNAPSHOT</version> | |
23 | + <version>2.5.5-SNAPSHOT</version> | |
24 | 24 | <artifactId>common</artifactId> |
25 | 25 | </parent> |
26 | 26 | <groupId>org.thingsboard.common</groupId> | ... | ... |
... | ... | @@ -20,7 +20,7 @@ |
20 | 20 | <modelVersion>4.0.0</modelVersion> |
21 | 21 | <parent> |
22 | 22 | <groupId>org.thingsboard</groupId> |
23 | - <version>2.5.3-SNAPSHOT</version> | |
23 | + <version>2.5.5-SNAPSHOT</version> | |
24 | 24 | <artifactId>common</artifactId> |
25 | 25 | </parent> |
26 | 26 | <groupId>org.thingsboard.common</groupId> | ... | ... |
... | ... | @@ -16,14 +16,12 @@ |
16 | 16 | package org.thingsboard.server.dao.audit; |
17 | 17 | |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | -import org.thingsboard.server.common.data.BaseData; | |
20 | 19 | import org.thingsboard.server.common.data.HasName; |
21 | 20 | import org.thingsboard.server.common.data.audit.ActionType; |
22 | 21 | import org.thingsboard.server.common.data.audit.AuditLog; |
23 | 22 | import org.thingsboard.server.common.data.id.CustomerId; |
24 | 23 | import org.thingsboard.server.common.data.id.EntityId; |
25 | 24 | import org.thingsboard.server.common.data.id.TenantId; |
26 | -import org.thingsboard.server.common.data.id.UUIDBased; | |
27 | 25 | import org.thingsboard.server.common.data.id.UserId; |
28 | 26 | import org.thingsboard.server.common.data.page.TimePageData; |
29 | 27 | import org.thingsboard.server.common.data.page.TimePageLink; |
... | ... | @@ -49,5 +47,4 @@ public interface AuditLogService { |
49 | 47 | E entity, |
50 | 48 | ActionType actionType, |
51 | 49 | Exception e, Object... additionalInfo); |
52 | - | |
53 | 50 | } | ... | ... |
... | ... | @@ -68,6 +68,8 @@ public interface DeviceService { |
68 | 68 | |
69 | 69 | ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId); |
70 | 70 | |
71 | + Device assignDeviceToTenant(TenantId tenantId, Device device); | |
72 | + | |
71 | 73 | Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); |
72 | 74 | |
73 | 75 | Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); | ... | ... |
... | ... | @@ -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> | ... | ... |
common/message/src/main/java/org/thingsboard/server/common/msg/kv/AttributesKVMsg.java
deleted
100644 → 0
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 | -} |
common/message/src/main/java/org/thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java
deleted
100644 → 0
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 | ... | ... |
... | ... | @@ -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 | ... | ... |
docker/docker-compose.confluent.yml
0 → 100644
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 | ... | ... |
docker/queue-confluent.env
0 → 100644
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 | ... | ... |
... | ... | @@ -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(); | ... | ... |
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java
0 → 100644
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", | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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> | ... | ... |