Commit 0f66d738c62c98f94def051de575fd962907f3fc

Authored by Vladyslav_Prykhodko
2 parents f2af7d08 c4b89e29

Merge upstream/master

Showing 85 changed files with 3761 additions and 383 deletions

Too many changes to show.

To preserve performance only 85 of 225 files are displayed.

@@ -43,7 +43,8 @@ @@ -43,7 +43,8 @@
43 "name": "Save Client Attributes", 43 "name": "Save Client Attributes",
44 "debugMode": false, 44 "debugMode": false,
45 "configuration": { 45 "configuration": {
46 - "scope": "CLIENT_SCOPE" 46 + "scope": "CLIENT_SCOPE",
  47 + "notifyDevice": "false"
47 } 48 }
48 }, 49 },
49 { 50 {
@@ -94,7 +94,7 @@ @@ -94,7 +94,7 @@
94 "name": "Device Profile Node", 94 "name": "Device Profile Node",
95 "debugMode": false, 95 "debugMode": false,
96 "configuration": { 96 "configuration": {
97 - "version": 0 97 + "persistAlarmRulesState": false
98 } 98 }
99 } 99 }
100 ], 100 ],
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 "configuration": null 8 "configuration": null
9 }, 9 },
10 "metadata": { 10 "metadata": {
11 - "firstNodeIndex": 2, 11 + "firstNodeIndex": 6,
12 "nodes": [ 12 "nodes": [
13 { 13 {
14 "additionalInfo": { 14 "additionalInfo": {
@@ -31,7 +31,8 @@ @@ -31,7 +31,8 @@
31 "name": "Save Client Attributes", 31 "name": "Save Client Attributes",
32 "debugMode": false, 32 "debugMode": false,
33 "configuration": { 33 "configuration": {
34 - "scope": "CLIENT_SCOPE" 34 + "scope": "CLIENT_SCOPE",
  35 + "notifyDevice": "false"
35 } 36 }
36 }, 37 },
37 { 38 {
@@ -81,10 +82,29 @@ @@ -81,10 +82,29 @@
81 "configuration": { 82 "configuration": {
82 "timeoutInSeconds": 60 83 "timeoutInSeconds": 60
83 } 84 }
  85 + },
  86 + {
  87 + "additionalInfo": {
  88 + "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
  89 + "layoutX": 204,
  90 + "layoutY": 240
  91 + },
  92 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
  93 + "name": "Device Profile Node",
  94 + "debugMode": false,
  95 + "configuration": {
  96 + "persistAlarmRulesState": false,
  97 + "fetchAlarmRulesStateOnStart": false
  98 + }
84 } 99 }
85 ], 100 ],
86 "connections": [ 101 "connections": [
87 { 102 {
  103 + "fromIndex": 6,
  104 + "toIndex": 2,
  105 + "type": "Success"
  106 + },
  107 + {
88 "fromIndex": 2, 108 "fromIndex": 2,
89 "toIndex": 4, 109 "toIndex": 4,
90 "type": "Other" 110 "type": "Other"
application/src/main/data/upgrade/3.1.1/schema_update_after.sql renamed from application/src/main/data/upgrade/3.1.2/schema_update_after.sql
application/src/main/data/upgrade/3.1.1/schema_update_before.sql renamed from application/src/main/data/upgrade/3.1.2/schema_update_before.sql
@@ -58,6 +58,7 @@ import org.thingsboard.server.dao.event.EventService; @@ -58,6 +58,7 @@ import org.thingsboard.server.dao.event.EventService;
58 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; 58 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
59 import org.thingsboard.server.dao.relation.RelationService; 59 import org.thingsboard.server.dao.relation.RelationService;
60 import org.thingsboard.server.dao.rule.RuleChainService; 60 import org.thingsboard.server.dao.rule.RuleChainService;
  61 +import org.thingsboard.server.dao.rule.RuleNodeStateService;
61 import org.thingsboard.server.dao.tenant.TenantProfileService; 62 import org.thingsboard.server.dao.tenant.TenantProfileService;
62 import org.thingsboard.server.dao.tenant.TenantService; 63 import org.thingsboard.server.dao.tenant.TenantService;
63 import org.thingsboard.server.dao.timeseries.TimeseriesService; 64 import org.thingsboard.server.dao.timeseries.TimeseriesService;
@@ -159,6 +160,10 @@ public class ActorSystemContext { @@ -159,6 +160,10 @@ public class ActorSystemContext {
159 private RuleChainService ruleChainService; 160 private RuleChainService ruleChainService;
160 161
161 @Autowired 162 @Autowired
  163 + @Getter
  164 + private RuleNodeStateService ruleNodeStateService;
  165 +
  166 + @Autowired
162 private PartitionService partitionService; 167 private PartitionService partitionService;
163 168
164 @Autowired 169 @Autowired
@@ -40,13 +40,15 @@ import org.thingsboard.server.common.data.id.EntityId; @@ -40,13 +40,15 @@ import org.thingsboard.server.common.data.id.EntityId;
40 import org.thingsboard.server.common.data.id.RuleChainId; 40 import org.thingsboard.server.common.data.id.RuleChainId;
41 import org.thingsboard.server.common.data.id.RuleNodeId; 41 import org.thingsboard.server.common.data.id.RuleNodeId;
42 import org.thingsboard.server.common.data.id.TenantId; 42 import org.thingsboard.server.common.data.id.TenantId;
  43 +import org.thingsboard.server.common.data.page.PageData;
  44 +import org.thingsboard.server.common.data.page.PageLink;
43 import org.thingsboard.server.common.data.rule.RuleNode; 45 import org.thingsboard.server.common.data.rule.RuleNode;
  46 +import org.thingsboard.server.common.data.rule.RuleNodeState;
44 import org.thingsboard.server.common.msg.TbActorMsg; 47 import org.thingsboard.server.common.msg.TbActorMsg;
45 import org.thingsboard.server.common.msg.TbMsg; 48 import org.thingsboard.server.common.msg.TbMsg;
46 import org.thingsboard.server.common.msg.TbMsgMetaData; 49 import org.thingsboard.server.common.msg.TbMsgMetaData;
47 import org.thingsboard.server.common.msg.queue.ServiceType; 50 import org.thingsboard.server.common.msg.queue.ServiceType;
48 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 51 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
49 -import org.thingsboard.server.dao.alarm.AlarmService;  
50 import org.thingsboard.server.dao.asset.AssetService; 52 import org.thingsboard.server.dao.asset.AssetService;
51 import org.thingsboard.server.dao.attributes.AttributesService; 53 import org.thingsboard.server.dao.attributes.AttributesService;
52 import org.thingsboard.server.dao.cassandra.CassandraCluster; 54 import org.thingsboard.server.dao.cassandra.CassandraCluster;
@@ -68,7 +70,6 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; @@ -68,7 +70,6 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
68 70
69 import java.util.Collections; 71 import java.util.Collections;
70 import java.util.Set; 72 import java.util.Set;
71 -import java.util.concurrent.TimeUnit;  
72 import java.util.function.Consumer; 73 import java.util.function.Consumer;
73 74
74 /** 75 /**
@@ -124,7 +125,7 @@ class DefaultTbContext implements TbContext { @@ -124,7 +125,7 @@ class DefaultTbContext implements TbContext {
124 125
125 @Override 126 @Override
126 public void enqueue(TbMsg tbMsg, String queueName, Runnable onSuccess, Consumer<Throwable> onFailure) { 127 public void enqueue(TbMsg tbMsg, String queueName, Runnable onSuccess, Consumer<Throwable> onFailure) {
127 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); 128 + TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
128 enqueue(tpi, tbMsg, onFailure, onSuccess); 129 enqueue(tpi, tbMsg, onFailure, onSuccess);
129 } 130 }
130 131
@@ -141,46 +142,54 @@ class DefaultTbContext implements TbContext { @@ -141,46 +142,54 @@ class DefaultTbContext implements TbContext {
141 142
142 @Override 143 @Override
143 public void enqueueForTellFailure(TbMsg tbMsg, String failureMessage) { 144 public void enqueueForTellFailure(TbMsg tbMsg, String failureMessage) {
144 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); 145 + TopicPartitionInfo tpi = resolvePartition(tbMsg);
145 enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), failureMessage, null, null); 146 enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), failureMessage, null, null);
146 } 147 }
147 148
148 @Override 149 @Override
149 public void enqueueForTellNext(TbMsg tbMsg, String relationType) { 150 public void enqueueForTellNext(TbMsg tbMsg, String relationType) {
150 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); 151 + TopicPartitionInfo tpi = resolvePartition(tbMsg);
151 enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, null, null); 152 enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, null, null);
152 } 153 }
153 154
154 @Override 155 @Override
155 public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes) { 156 public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes) {
156 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); 157 + TopicPartitionInfo tpi = resolvePartition(tbMsg);
157 enqueueForTellNext(tpi, tbMsg, relationTypes, null, null, null); 158 enqueueForTellNext(tpi, tbMsg, relationTypes, null, null, null);
158 } 159 }
159 160
160 @Override 161 @Override
161 public void enqueueForTellNext(TbMsg tbMsg, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) { 162 public void enqueueForTellNext(TbMsg tbMsg, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) {
162 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); 163 + TopicPartitionInfo tpi = resolvePartition(tbMsg);
163 enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure); 164 enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure);
164 } 165 }
165 166
166 @Override 167 @Override
167 public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) { 168 public void enqueueForTellNext(TbMsg tbMsg, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) {
168 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); 169 + TopicPartitionInfo tpi = resolvePartition(tbMsg);
169 enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure); 170 enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure);
170 } 171 }
171 172
172 @Override 173 @Override
173 public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) { 174 public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) {
174 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); 175 + TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
175 enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure); 176 enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure);
176 } 177 }
177 178
178 @Override 179 @Override
179 public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) { 180 public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) {
180 - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); 181 + TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
181 enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure); 182 enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure);
182 } 183 }
183 184
  185 + private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) {
  186 + return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator());
  187 + }
  188 +
  189 + private TopicPartitionInfo resolvePartition(TbMsg tbMsg) {
  190 + return resolvePartition(tbMsg, tbMsg.getQueueName());
  191 + }
  192 +
184 private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) { 193 private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
185 RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId(); 194 RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
186 RuleNodeId ruleNodeId = nodeCtx.getSelf().getId(); 195 RuleNodeId ruleNodeId = nodeCtx.getSelf().getId();
@@ -430,6 +439,30 @@ class DefaultTbContext implements TbContext { @@ -430,6 +439,30 @@ class DefaultTbContext implements TbContext {
430 return mainCtx.getRedisTemplate(); 439 return mainCtx.getRedisTemplate();
431 } 440 }
432 441
  442 + @Override
  443 + public PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink) {
  444 + if (log.isDebugEnabled()) {
  445 + log.debug("[{}][{}] Fetch Rule Node States.", getTenantId(), getSelfId());
  446 + }
  447 + return mainCtx.getRuleNodeStateService().findByRuleNodeId(getTenantId(), getSelfId(), pageLink);
  448 + }
  449 +
  450 + @Override
  451 + public RuleNodeState findRuleNodeStateForEntity(EntityId entityId) {
  452 + if (log.isDebugEnabled()) {
  453 + log.debug("[{}][{}][{}] Fetch Rule Node State for entity.", getTenantId(), getSelfId(), entityId);
  454 + }
  455 + return mainCtx.getRuleNodeStateService().findByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId);
  456 + }
  457 +
  458 + @Override
  459 + public RuleNodeState saveRuleNodeState(RuleNodeState state) {
  460 + if (log.isDebugEnabled()) {
  461 + log.debug("[{}][{}][{}] Persist Rule Node State for entity: {}", getTenantId(), getSelfId(), state.getEntityId(), state.getStateData());
  462 + }
  463 + state.setRuleNodeId(getSelfId());
  464 + return mainCtx.getRuleNodeStateService().save(getTenantId(), state);
  465 + }
433 466
434 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { 467 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
435 TbMsgMetaData metaData = new TbMsgMetaData(); 468 TbMsgMetaData metaData = new TbMsgMetaData();
@@ -47,6 +47,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; @@ -47,6 +47,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
47 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; 47 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
48 import org.thingsboard.server.common.msg.queue.RuleEngineException; 48 import org.thingsboard.server.common.msg.queue.RuleEngineException;
49 import org.thingsboard.server.common.msg.queue.ServiceType; 49 import org.thingsboard.server.common.msg.queue.ServiceType;
  50 +import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
50 51
51 import java.util.List; 52 import java.util.List;
52 import java.util.Optional; 53 import java.util.Optional;
@@ -116,6 +117,9 @@ public class TenantActor extends RuleChainManagerActor { @@ -116,6 +117,9 @@ public class TenantActor extends RuleChainManagerActor {
116 if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) { 117 if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) {
117 QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg; 118 QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg;
118 queueMsg.getTbMsg().getCallback().onSuccess(); 119 queueMsg.getTbMsg().getCallback().onSuccess();
  120 + } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)){
  121 + TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg;
  122 + transportMsg.getCallback().onSuccess();
119 } 123 }
120 return true; 124 return true;
121 } 125 }
@@ -90,7 +90,7 @@ public class AlarmController extends BaseController { @@ -90,7 +90,7 @@ public class AlarmController extends BaseController {
90 checkEntity(alarm.getId(), alarm, Resource.ALARM); 90 checkEntity(alarm.getId(), alarm, Resource.ALARM);
91 91
92 Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm)); 92 Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
93 - logEntityAction(savedAlarm.getId(), savedAlarm, 93 + logEntityAction(savedAlarm.getOriginator(), savedAlarm,
94 getCurrentUser().getCustomerId(), 94 getCurrentUser().getCustomerId(),
95 alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); 95 alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
96 return savedAlarm; 96 return savedAlarm;
@@ -126,7 +126,7 @@ public class AlarmController extends BaseController { @@ -126,7 +126,7 @@ public class AlarmController extends BaseController {
126 long ackTs = System.currentTimeMillis(); 126 long ackTs = System.currentTimeMillis();
127 alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get(); 127 alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get();
128 alarm.setAckTs(ackTs); 128 alarm.setAckTs(ackTs);
129 - logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null); 129 + logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
130 } catch (Exception e) { 130 } catch (Exception e) {
131 throw handleException(e); 131 throw handleException(e);
132 } 132 }
@@ -143,7 +143,7 @@ public class AlarmController extends BaseController { @@ -143,7 +143,7 @@ public class AlarmController extends BaseController {
143 long clearTs = System.currentTimeMillis(); 143 long clearTs = System.currentTimeMillis();
144 alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get(); 144 alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get();
145 alarm.setClearTs(clearTs); 145 alarm.setClearTs(clearTs);
146 - logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null); 146 + logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
147 } catch (Exception e) { 147 } catch (Exception e) {
148 throw handleException(e); 148 throw handleException(e);
149 } 149 }
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.beans.factory.annotation.Value; 24 import org.springframework.beans.factory.annotation.Value;
25 import org.springframework.http.HttpStatus; 25 import org.springframework.http.HttpStatus;
26 import org.springframework.security.access.prepost.PreAuthorize; 26 import org.springframework.security.access.prepost.PreAuthorize;
  27 +import org.springframework.util.CollectionUtils;
27 import org.springframework.util.StringUtils; 28 import org.springframework.util.StringUtils;
28 import org.springframework.web.bind.annotation.PathVariable; 29 import org.springframework.web.bind.annotation.PathVariable;
29 import org.springframework.web.bind.annotation.RequestBody; 30 import org.springframework.web.bind.annotation.RequestBody;
@@ -49,6 +50,8 @@ import org.thingsboard.server.common.data.page.PageLink; @@ -49,6 +50,8 @@ import org.thingsboard.server.common.data.page.PageLink;
49 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 50 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
50 import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest; 51 import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
51 import org.thingsboard.server.common.data.rule.RuleChain; 52 import org.thingsboard.server.common.data.rule.RuleChain;
  53 +import org.thingsboard.server.common.data.rule.RuleChainData;
  54 +import org.thingsboard.server.common.data.rule.RuleChainImportResult;
52 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 55 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
53 import org.thingsboard.server.common.data.rule.RuleNode; 56 import org.thingsboard.server.common.data.rule.RuleNode;
54 import org.thingsboard.server.common.msg.TbMsg; 57 import org.thingsboard.server.common.msg.TbMsg;
@@ -386,6 +389,36 @@ public class RuleChainController extends BaseController { @@ -386,6 +389,36 @@ public class RuleChainController extends BaseController {
386 } 389 }
387 } 390 }
388 391
  392 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  393 + @RequestMapping(value = "/ruleChains/export", params = {"limit"}, method = RequestMethod.GET)
  394 + @ResponseBody
  395 + public RuleChainData exportRuleChains(@RequestParam("limit") int limit) throws ThingsboardException {
  396 + try {
  397 + TenantId tenantId = getCurrentUser().getTenantId();
  398 + PageLink pageLink = new PageLink(limit);
  399 + return checkNotNull(ruleChainService.exportTenantRuleChains(tenantId, pageLink));
  400 + } catch (Exception e) {
  401 + throw handleException(e);
  402 + }
  403 + }
  404 +
  405 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  406 + @RequestMapping(value = "/ruleChains/import", method = RequestMethod.POST)
  407 + @ResponseBody
  408 + public void importRuleChains(@RequestBody RuleChainData ruleChainData, @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException {
  409 + try {
  410 + TenantId tenantId = getCurrentUser().getTenantId();
  411 + List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite);
  412 + if (!CollectionUtils.isEmpty(importResults)) {
  413 + for (RuleChainImportResult importResult : importResults) {
  414 + tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent());
  415 + }
  416 + }
  417 + } catch (Exception e) {
  418 + throw handleException(e);
  419 + }
  420 + }
  421 +
389 private String msgToOutput(TbMsg msg) throws Exception { 422 private String msgToOutput(TbMsg msg) throws Exception {
390 ObjectNode msgData = objectMapper.createObjectNode(); 423 ObjectNode msgData = objectMapper.createObjectNode();
391 if (!StringUtils.isEmpty(msg.getData())) { 424 if (!StringUtils.isEmpty(msg.getData())) {
@@ -176,13 +176,12 @@ public class ThingsboardInstallService { @@ -176,13 +176,12 @@ public class ThingsboardInstallService {
176 log.info("Upgrading ThingsBoard from version 3.1.0 to 3.1.1 ..."); 176 log.info("Upgrading ThingsBoard from version 3.1.0 to 3.1.1 ...");
177 databaseEntitiesUpgradeService.upgradeDatabase("3.1.0"); 177 databaseEntitiesUpgradeService.upgradeDatabase("3.1.0");
178 case "3.1.1": 178 case "3.1.1":
179 - log.info("Upgrading ThingsBoard from version 3.1.1 to 3.1.2 ..."); 179 + log.info("Upgrading ThingsBoard from version 3.1.1 to 3.2.0 ...");
180 if (databaseTsUpgradeService != null) { 180 if (databaseTsUpgradeService != null) {
181 databaseTsUpgradeService.upgradeDatabase("3.1.1"); 181 databaseTsUpgradeService.upgradeDatabase("3.1.1");
182 } 182 }
183 - case "3.1.2":  
184 - log.info("Upgrading ThingsBoard from version 3.1.2 to 3.2.0 ...");  
185 - databaseEntitiesUpgradeService.upgradeDatabase("3.1.2"); 183 + databaseEntitiesUpgradeService.upgradeDatabase("3.1.1");
  184 + dataUpdateService.updateData("3.1.1");
186 log.info("Updating system data..."); 185 log.info("Updating system data...");
187 systemDataLoaderService.updateSystemWidgets(); 186 systemDataLoaderService.updateSystemWidgets();
188 break; 187 break;
@@ -324,7 +324,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -324,7 +324,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
324 log.info("Schema updated."); 324 log.info("Schema updated.");
325 } 325 }
326 break; 326 break;
327 - case "3.1.2": 327 + case "3.1.1":
328 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 328 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
329 log.info("Updating schema ..."); 329 log.info("Updating schema ...");
330 if (isOldSchema(conn, 3001000)) { 330 if (isOldSchema(conn, 3001000)) {
@@ -339,7 +339,20 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -339,7 +339,20 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
339 } catch (Exception e) { 339 } catch (Exception e) {
340 } 340 }
341 341
342 - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.2", "schema_update_before.sql"); 342 + try {
  343 + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS rule_node_state (" +
  344 + " id uuid NOT NULL CONSTRAINT rule_node_state_pkey PRIMARY KEY," +
  345 + " created_time bigint NOT NULL," +
  346 + " rule_node_id uuid NOT NULL," +
  347 + " entity_type varchar(32) NOT NULL," +
  348 + " entity_id uuid NOT NULL," +
  349 + " state_data varchar(16384) NOT NULL," +
  350 + " CONSTRAINT rule_node_state_unq_key UNIQUE (rule_node_id, entity_id)," +
  351 + " CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE)");
  352 + } catch (Exception e) {
  353 + }
  354 +
  355 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_before.sql");
343 loadSql(schemaUpdateFile, conn); 356 loadSql(schemaUpdateFile, conn);
344 357
345 log.info("Creating default tenant profiles..."); 358 log.info("Creating default tenant profiles...");
@@ -357,7 +370,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -357,7 +370,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
357 List<EntitySubtype> deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get(); 370 List<EntitySubtype> deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get();
358 try { 371 try {
359 deviceProfileService.createDefaultDeviceProfile(tenant.getId()); 372 deviceProfileService.createDefaultDeviceProfile(tenant.getId());
360 - } catch (Exception e){} 373 + } catch (Exception e) {
  374 + }
361 for (EntitySubtype deviceType : deviceTypes) { 375 for (EntitySubtype deviceType : deviceTypes) {
362 try { 376 try {
363 deviceProfileService.findOrCreateDeviceProfile(tenant.getId(), deviceType.getType()); 377 deviceProfileService.findOrCreateDeviceProfile(tenant.getId(), deviceType.getType());
@@ -371,7 +385,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -371,7 +385,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
371 log.info("Updating device profiles..."); 385 log.info("Updating device profiles...");
372 conn.createStatement().execute("call update_device_profiles()"); 386 conn.createStatement().execute("call update_device_profiles()");
373 387
374 - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.2", "schema_update_after.sql"); 388 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_after.sql");
375 loadSql(schemaUpdateFile, conn); 389 loadSql(schemaUpdateFile, conn);
376 390
377 conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002000;"); 391 conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002000;");
@@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
15 */ 15 */
16 package org.thingsboard.server.service.install.update; 16 package org.thingsboard.server.service.install.update;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 import com.google.common.util.concurrent.FutureCallback; 20 import com.google.common.util.concurrent.FutureCallback;
19 import com.google.common.util.concurrent.Futures; 21 import com.google.common.util.concurrent.Futures;
20 import com.google.common.util.concurrent.ListenableFuture; 22 import com.google.common.util.concurrent.ListenableFuture;
@@ -23,9 +25,13 @@ import lombok.extern.slf4j.Slf4j; @@ -23,9 +25,13 @@ import lombok.extern.slf4j.Slf4j;
23 import org.springframework.beans.factory.annotation.Autowired; 25 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.context.annotation.Profile; 26 import org.springframework.context.annotation.Profile;
25 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
  28 +import org.springframework.util.StringUtils;
  29 +import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
  30 +import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
26 import org.thingsboard.server.common.data.EntityView; 31 import org.thingsboard.server.common.data.EntityView;
27 import org.thingsboard.server.common.data.SearchTextBased; 32 import org.thingsboard.server.common.data.SearchTextBased;
28 import org.thingsboard.server.common.data.Tenant; 33 import org.thingsboard.server.common.data.Tenant;
  34 +import org.thingsboard.server.common.data.id.EntityId;
29 import org.thingsboard.server.common.data.id.EntityViewId; 35 import org.thingsboard.server.common.data.id.EntityViewId;
30 import org.thingsboard.server.common.data.id.TenantId; 36 import org.thingsboard.server.common.data.id.TenantId;
31 import org.thingsboard.server.common.data.id.UUIDBased; 37 import org.thingsboard.server.common.data.id.UUIDBased;
@@ -35,10 +41,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -35,10 +41,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
35 import org.thingsboard.server.common.data.page.PageData; 41 import org.thingsboard.server.common.data.page.PageData;
36 import org.thingsboard.server.common.data.page.PageLink; 42 import org.thingsboard.server.common.data.page.PageLink;
37 import org.thingsboard.server.common.data.rule.RuleChain; 43 import org.thingsboard.server.common.data.rule.RuleChain;
  44 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  45 +import org.thingsboard.server.common.data.rule.RuleNode;
38 import org.thingsboard.server.dao.entityview.EntityViewService; 46 import org.thingsboard.server.dao.entityview.EntityViewService;
39 import org.thingsboard.server.dao.rule.RuleChainService; 47 import org.thingsboard.server.dao.rule.RuleChainService;
40 import org.thingsboard.server.dao.tenant.TenantService; 48 import org.thingsboard.server.dao.tenant.TenantService;
41 import org.thingsboard.server.dao.timeseries.TimeseriesService; 49 import org.thingsboard.server.dao.timeseries.TimeseriesService;
  50 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
42 import org.thingsboard.server.service.install.InstallScripts; 51 import org.thingsboard.server.service.install.InstallScripts;
43 52
44 import javax.annotation.Nullable; 53 import javax.annotation.Nullable;
@@ -49,6 +58,7 @@ import java.util.concurrent.ExecutionException; @@ -49,6 +58,7 @@ import java.util.concurrent.ExecutionException;
49 import java.util.stream.Collectors; 58 import java.util.stream.Collectors;
50 59
51 import static org.apache.commons.lang.StringUtils.isBlank; 60 import static org.apache.commons.lang.StringUtils.isBlank;
  61 +import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
52 62
53 @Service 63 @Service
54 @Profile("install") 64 @Profile("install")
@@ -81,6 +91,10 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -81,6 +91,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
81 log.info("Updating data from version 3.0.1 to 3.1.0 ..."); 91 log.info("Updating data from version 3.0.1 to 3.1.0 ...");
82 tenantsEntityViewsUpdater.updateEntities(null); 92 tenantsEntityViewsUpdater.updateEntities(null);
83 break; 93 break;
  94 + case "3.1.1":
  95 + log.info("Updating data from version 3.1.1 to 3.2.0 ...");
  96 + tenantsRootRuleChainUpdater.updateEntities(null);
  97 + break;
84 default: 98 default:
85 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); 99 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
86 } 100 }
@@ -107,6 +121,60 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -107,6 +121,60 @@ public class DefaultDataUpdateService implements DataUpdateService {
107 } 121 }
108 }; 122 };
109 123
  124 + private PaginatedUpdater<String, Tenant> tenantsRootRuleChainUpdater =
  125 + new PaginatedUpdater<String, Tenant>() {
  126 +
  127 + @Override
  128 + protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
  129 + return tenantService.findTenants(pageLink);
  130 + }
  131 +
  132 + @Override
  133 + protected void updateEntity(Tenant tenant) {
  134 + try {
  135 + RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId());
  136 + if (ruleChain == null) {
  137 + installScripts.createDefaultRuleChains(tenant.getId());
  138 + } else {
  139 + RuleChainMetaData md = ruleChainService.loadRuleChainMetaData(tenant.getId(), ruleChain.getId());
  140 + int oldIdx = md.getFirstNodeIndex();
  141 + int newIdx = md.getNodes().size();
  142 +
  143 + if (md.getNodes().size() < oldIdx) {
  144 + // Skip invalid rule chains
  145 + return;
  146 + }
  147 +
  148 + RuleNode oldFirstNode = md.getNodes().get(oldIdx);
  149 + if (oldFirstNode.getType().equals(TbDeviceProfileNode.class.getName())) {
  150 + // No need to update the rule node twice.
  151 + return;
  152 + }
  153 +
  154 + RuleNode ruleNode = new RuleNode();
  155 + ruleNode.setRuleChainId(ruleChain.getId());
  156 + ruleNode.setName("Device Profile Node");
  157 + ruleNode.setType(TbDeviceProfileNode.class.getName());
  158 + ruleNode.setDebugMode(false);
  159 + TbDeviceProfileNodeConfiguration ruleNodeConfiguration = new TbDeviceProfileNodeConfiguration().defaultConfiguration();
  160 + ruleNode.setConfiguration(JacksonUtil.valueToTree(ruleNodeConfiguration));
  161 + ObjectNode additionalInfo = JacksonUtil.newObjectNode();
  162 + additionalInfo.put("description", "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.");
  163 + additionalInfo.put("layoutX", 204);
  164 + additionalInfo.put("layoutY", 240);
  165 + ruleNode.setAdditionalInfo(additionalInfo);
  166 +
  167 + md.getNodes().add(ruleNode);
  168 + md.setFirstNodeIndex(newIdx);
  169 + md.addConnectionInfo(newIdx, oldIdx, "Success");
  170 + ruleChainService.saveRuleChainMetaData(tenant.getId(), md);
  171 + }
  172 + } catch (Exception e) {
  173 + log.error("Unable to update Tenant", e);
  174 + }
  175 + }
  176 + };
  177 +
110 private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater = 178 private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater =
111 new PaginatedUpdater<String, Tenant>() { 179 new PaginatedUpdater<String, Tenant>() {
112 180
@@ -121,30 +189,30 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -121,30 +189,30 @@ public class DefaultDataUpdateService implements DataUpdateService {
121 } 189 }
122 }; 190 };
123 191
124 - private void updateTenantEntityViews(TenantId tenantId) {  
125 - PageLink pageLink = new PageLink(100);  
126 - PageData<EntityView> pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);  
127 - boolean hasNext = true;  
128 - while (hasNext) {  
129 - List<ListenableFuture<List<Void>>> updateFutures = new ArrayList<>();  
130 - for (EntityView entityView : pageData.getData()) {  
131 - updateFutures.add(updateEntityViewLatestTelemetry(entityView));  
132 - }  
133 -  
134 - try {  
135 - Futures.allAsList(updateFutures).get();  
136 - } catch (InterruptedException | ExecutionException e) {  
137 - log.error("Failed to copy latest telemetry to entity view", e);  
138 - }  
139 -  
140 - if (pageData.hasNext()) {  
141 - pageLink = pageLink.nextPageLink();  
142 - pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);  
143 - } else {  
144 - hasNext = false;  
145 - }  
146 - }  
147 - } 192 + private void updateTenantEntityViews(TenantId tenantId) {
  193 + PageLink pageLink = new PageLink(100);
  194 + PageData<EntityView> pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
  195 + boolean hasNext = true;
  196 + while (hasNext) {
  197 + List<ListenableFuture<List<Void>>> updateFutures = new ArrayList<>();
  198 + for (EntityView entityView : pageData.getData()) {
  199 + updateFutures.add(updateEntityViewLatestTelemetry(entityView));
  200 + }
  201 +
  202 + try {
  203 + Futures.allAsList(updateFutures).get();
  204 + } catch (InterruptedException | ExecutionException e) {
  205 + log.error("Failed to copy latest telemetry to entity view", e);
  206 + }
  207 +
  208 + if (pageData.hasNext()) {
  209 + pageLink = pageLink.nextPageLink();
  210 + pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
  211 + } else {
  212 + hasNext = false;
  213 + }
  214 + }
  215 + }
148 216
149 private ListenableFuture<List<Void>> updateEntityViewLatestTelemetry(EntityView entityView) { 217 private ListenableFuture<List<Void>> updateEntityViewLatestTelemetry(EntityView entityView) {
150 EntityViewId entityId = entityView.getId(); 218 EntityViewId entityId = entityView.getId();
@@ -160,13 +228,13 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -160,13 +228,13 @@ public class DefaultDataUpdateService implements DataUpdateService {
160 keysFuture = Futures.immediateFuture(keys); 228 keysFuture = Futures.immediateFuture(keys);
161 } 229 }
162 ListenableFuture<List<TsKvEntry>> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> { 230 ListenableFuture<List<TsKvEntry>> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> {
163 - List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());  
164 - if (!queries.isEmpty()) {  
165 - return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries);  
166 - } else {  
167 - return Futures.immediateFuture(null);  
168 - }  
169 - }, MoreExecutors.directExecutor()); 231 + List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());
  232 + if (!queries.isEmpty()) {
  233 + return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries);
  234 + } else {
  235 + return Futures.immediateFuture(null);
  236 + }
  237 + }, MoreExecutors.directExecutor());
170 return Futures.transformAsync(latestFuture, latestValues -> { 238 return Futures.transformAsync(latestFuture, latestValues -> {
171 if (latestValues != null && !latestValues.isEmpty()) { 239 if (latestValues != null && !latestValues.isEmpty()) {
172 ListenableFuture<List<Void>> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues); 240 ListenableFuture<List<Void>> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues);
@@ -50,9 +50,9 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { @@ -50,9 +50,9 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
50 public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) { 50 public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) {
51 DeviceProfile profile = deviceProfilesMap.get(deviceProfileId); 51 DeviceProfile profile = deviceProfilesMap.get(deviceProfileId);
52 if (profile == null) { 52 if (profile == null) {
53 - deviceProfileFetchLock.lock();  
54 profile = deviceProfilesMap.get(deviceProfileId); 53 profile = deviceProfilesMap.get(deviceProfileId);
55 if (profile == null) { 54 if (profile == null) {
  55 + deviceProfileFetchLock.lock();
56 try { 56 try {
57 profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); 57 profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
58 if (profile != null) { 58 if (profile != null) {
@@ -226,6 +226,11 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer @@ -226,6 +226,11 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
226 226
227 @Override 227 @Override
228 public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback) { 228 public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback) {
  229 + onAttributesUpdate(tenantId, entityId, scope, attributes, true, callback);
  230 + }
  231 +
  232 + @Override
  233 + public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback) {
229 onLocalTelemetrySubUpdate(entityId, 234 onLocalTelemetrySubUpdate(entityId,
230 s -> { 235 s -> {
231 if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) { 236 if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
@@ -254,7 +259,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer @@ -254,7 +259,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
254 deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L)); 259 deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
255 } 260 }
256 } 261 }
257 - } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope)) { 262 + } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
258 clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, 263 clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
259 new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) 264 new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
260 , null); 265 , null);
@@ -17,13 +17,12 @@ package org.thingsboard.server.service.subscription; @@ -17,13 +17,12 @@ package org.thingsboard.server.service.subscription;
17 17
18 import org.springframework.context.ApplicationListener; 18 import org.springframework.context.ApplicationListener;
19 import org.thingsboard.server.common.data.alarm.Alarm; 19 import org.thingsboard.server.common.data.alarm.Alarm;
20 -import org.thingsboard.server.common.data.id.AlarmId;  
21 import org.thingsboard.server.common.data.id.EntityId; 20 import org.thingsboard.server.common.data.id.EntityId;
22 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
23 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
24 import org.thingsboard.server.common.data.kv.TsKvEntry; 23 import org.thingsboard.server.common.data.kv.TsKvEntry;
25 -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;  
26 import org.thingsboard.server.common.msg.queue.TbCallback; 24 import org.thingsboard.server.common.msg.queue.TbCallback;
  25 +import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
27 26
28 import java.util.List; 27 import java.util.List;
29 28
@@ -37,9 +36,13 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio @@ -37,9 +36,13 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
37 36
38 void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback); 37 void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback);
39 38
  39 + void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback);
  40 +
40 void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty); 41 void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
41 42
42 void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); 43 void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
43 44
44 void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); 45 void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
  46 +
  47 +
45 } 48 }
@@ -171,9 +171,14 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @@ -171,9 +171,14 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
171 171
172 @Override 172 @Override
173 public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) { 173 public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
  174 + saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
  175 + }
  176 +
  177 + @Override
  178 + public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
174 ListenableFuture<List<Void>> saveFuture = attrService.save(tenantId, entityId, scope, attributes); 179 ListenableFuture<List<Void>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
175 addMainCallback(saveFuture, callback); 180 addMainCallback(saveFuture, callback);
176 - addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes)); 181 + addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
177 } 182 }
178 183
179 @Override 184 @Override
@@ -236,11 +241,11 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @@ -236,11 +241,11 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
236 , System.currentTimeMillis())), callback); 241 , System.currentTimeMillis())), callback);
237 } 242 }
238 243
239 - private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) { 244 + private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
240 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); 245 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
241 if (currentPartitions.contains(tpi)) { 246 if (currentPartitions.contains(tpi)) {
242 if (subscriptionManagerService.isPresent()) { 247 if (subscriptionManagerService.isPresent()) {
243 - subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, TbCallback.EMPTY); 248 + subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
244 } else { 249 } else {
245 log.warn("Possible misconfiguration because subscriptionManagerService is null!"); 250 log.warn("Possible misconfiguration because subscriptionManagerService is null!");
246 } 251 }
@@ -225,6 +225,8 @@ public class DefaultTransportApiService implements TransportApiService { @@ -225,6 +225,8 @@ public class DefaultTransportApiService implements TransportApiService {
225 device.setName(requestMsg.getDeviceName()); 225 device.setName(requestMsg.getDeviceName());
226 device.setType(requestMsg.getDeviceType()); 226 device.setType(requestMsg.getDeviceType());
227 device.setCustomerId(gateway.getCustomerId()); 227 device.setCustomerId(gateway.getCustomerId());
  228 + DeviceProfile deviceProfile = deviceProfileService.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType());
  229 + device.setDeviceProfileId(deviceProfile.getId());
228 device = deviceService.saveDevice(device); 230 device = deviceService.saveDevice(device);
229 relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); 231 relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
230 deviceStateService.onDeviceAdded(device); 232 deviceStateService.onDeviceAdded(device);
@@ -439,6 +439,9 @@ updates: @@ -439,6 +439,9 @@ updates:
439 # Enable/disable updates checking. 439 # Enable/disable updates checking.
440 enabled: "${UPDATES_ENABLED:true}" 440 enabled: "${UPDATES_ENABLED:true}"
441 441
  442 +# spring freemarker configuration
  443 +spring.freemarker.checkTemplateLocation: "false"
  444 +
442 # spring CORS configuration 445 # spring CORS configuration
443 spring.mvc.cors: 446 spring.mvc.cors:
444 mappings: 447 mappings:
@@ -605,6 +608,8 @@ transport: @@ -605,6 +608,8 @@ transport:
605 key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" 608 key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
606 # Type of the key store 609 # Type of the key store
607 key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" 610 key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
  611 + # Skip certificate validity check for client certificates.
  612 + skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
608 # Local CoAP transport parameters 613 # Local CoAP transport parameters
609 coap: 614 coap:
610 # Enable/disable coap transport protocol. 615 # Enable/disable coap transport protocol.
@@ -226,6 +226,10 @@ public abstract class AbstractWebTest { @@ -226,6 +226,10 @@ public abstract class AbstractWebTest {
226 login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD); 226 login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
227 } 227 }
228 228
  229 + protected void loginUser(String userName, String password) throws Exception {
  230 + login(userName, password);
  231 + }
  232 +
229 private Tenant savedDifferentTenant; 233 private Tenant savedDifferentTenant;
230 234
231 protected void loginDifferentTenant() throws Exception { 235 protected void loginDifferentTenant() throws Exception {
@@ -251,15 +255,27 @@ public abstract class AbstractWebTest { @@ -251,15 +255,27 @@ public abstract class AbstractWebTest {
251 protected User createUserAndLogin(User user, String password) throws Exception { 255 protected User createUserAndLogin(User user, String password) throws Exception {
252 User savedUser = doPost("/api/user", user, User.class); 256 User savedUser = doPost("/api/user", user, User.class);
253 logout(); 257 logout();
  258 + JsonNode activateRequest = getActivateRequest(password);
  259 + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
  260 + validateAndSetJwtToken(tokenInfo, user.getEmail());
  261 + return savedUser;
  262 + }
  263 +
  264 + protected User createUser(User user, String password) throws Exception {
  265 + User savedUser = doPost("/api/user", user, User.class);
  266 + JsonNode activateRequest = getActivateRequest(password);
  267 + ResultActions resultActions = doPost("/api/noauth/activate", activateRequest);
  268 + resultActions.andExpect(status().isOk());
  269 + return savedUser;
  270 + }
  271 +
  272 + private JsonNode getActivateRequest(String password) throws Exception {
254 doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) 273 doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
255 .andExpect(status().isSeeOther()) 274 .andExpect(status().isSeeOther())
256 .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); 275 .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
257 - JsonNode activateRequest = new ObjectMapper().createObjectNode() 276 + return new ObjectMapper().createObjectNode()
258 .put("activateToken", TestMailService.currentActivateToken) 277 .put("activateToken", TestMailService.currentActivateToken)
259 .put("password", password); 278 .put("password", password);
260 - JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);  
261 - validateAndSetJwtToken(tokenInfo, user.getEmail());  
262 - return savedUser;  
263 } 279 }
264 280
265 protected void login(String username, String password) throws Exception { 281 protected void login(String username, String password) throws Exception {
@@ -442,6 +458,10 @@ public abstract class AbstractWebTest { @@ -442,6 +458,10 @@ public abstract class AbstractWebTest {
442 return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass); 458 return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass);
443 } 459 }
444 460
  461 + protected <T> T doPostClaimAsync(String urlTemplate, Object content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
  462 + return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
  463 + }
  464 +
445 protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception { 465 protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
446 return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass); 466 return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass);
447 } 467 }
@@ -22,6 +22,7 @@ import org.apache.commons.lang3.RandomStringUtils; @@ -22,6 +22,7 @@ import org.apache.commons.lang3.RandomStringUtils;
22 import org.eclipse.paho.client.mqttv3.MqttAsyncClient; 22 import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
23 import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 23 import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
24 import org.eclipse.paho.client.mqttv3.MqttMessage; 24 import org.eclipse.paho.client.mqttv3.MqttMessage;
  25 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
25 import org.junit.After; 26 import org.junit.After;
26 import org.junit.Assert; 27 import org.junit.Assert;
27 import org.junit.Before; 28 import org.junit.Before;
@@ -424,7 +425,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -424,7 +425,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
424 assertNotNull(accessToken); 425 assertNotNull(accessToken);
425 426
426 String clientId = MqttAsyncClient.generateClientId(); 427 String clientId = MqttAsyncClient.generateClientId();
427 - MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId); 428 + MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
428 429
429 MqttConnectOptions options = new MqttConnectOptions(); 430 MqttConnectOptions options = new MqttConnectOptions();
430 options.setUserName(accessToken); 431 options.setUserName(accessToken);
@@ -466,7 +467,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -466,7 +467,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
466 assertNotNull(accessToken); 467 assertNotNull(accessToken);
467 468
468 String clientId = MqttAsyncClient.generateClientId(); 469 String clientId = MqttAsyncClient.generateClientId();
469 - MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId); 470 + MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
470 471
471 MqttConnectOptions options = new MqttConnectOptions(); 472 MqttConnectOptions options = new MqttConnectOptions();
472 options.setUserName(accessToken); 473 options.setUserName(accessToken);
@@ -34,7 +34,7 @@ public class ControllerSqlTestSuite { @@ -34,7 +34,7 @@ public class ControllerSqlTestSuite {
34 34
35 @ClassRule 35 @ClassRule
36 public static CustomSqlUnit sqlUnit = new CustomSqlUnit( 36 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
37 - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), 37 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
38 "sql/hsql/drop-all-tables.sql", 38 "sql/hsql/drop-all-tables.sql",
39 "sql-test.properties"); 39 "sql-test.properties");
40 40
  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.mqtt;
  17 +
  18 +import com.fasterxml.jackson.databind.node.ObjectNode;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  21 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  22 +import org.eclipse.paho.client.mqttv3.MqttException;
  23 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  24 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
  25 +import org.junit.Assert;
  26 +import org.springframework.util.StringUtils;
  27 +import org.thingsboard.server.common.data.Device;
  28 +import org.thingsboard.server.common.data.DeviceProfile;
  29 +import org.thingsboard.server.common.data.DeviceProfileType;
  30 +import org.thingsboard.server.common.data.DeviceTransportType;
  31 +import org.thingsboard.server.common.data.Tenant;
  32 +import org.thingsboard.server.common.data.TransportPayloadType;
  33 +import org.thingsboard.server.common.data.User;
  34 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  35 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  36 +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
  37 +import org.thingsboard.server.common.data.security.Authority;
  38 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  39 +import org.thingsboard.server.controller.AbstractControllerTest;
  40 +import org.thingsboard.server.gen.transport.TransportProtos;
  41 +
  42 +import java.util.ArrayList;
  43 +import java.util.List;
  44 +import java.util.concurrent.atomic.AtomicInteger;
  45 +import java.util.function.Supplier;
  46 +
  47 +import static org.junit.Assert.assertEquals;
  48 +import static org.junit.Assert.assertNotNull;
  49 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  50 +
  51 +@Slf4j
  52 +public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest {
  53 +
  54 + protected static final String MQTT_URL = "tcp://localhost:1883";
  55 +
  56 + private static final AtomicInteger atomicInteger = new AtomicInteger(2);
  57 +
  58 + protected Tenant savedTenant;
  59 + protected User tenantAdmin;
  60 +
  61 + protected Device savedDevice;
  62 + protected String accessToken;
  63 +
  64 + protected Device savedGateway;
  65 + protected String gatewayAccessToken;
  66 +
  67 + protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
  68 + loginSysAdmin();
  69 +
  70 + Tenant tenant = new Tenant();
  71 + tenant.setTitle("My tenant");
  72 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  73 + Assert.assertNotNull(savedTenant);
  74 +
  75 + tenantAdmin = new User();
  76 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  77 + tenantAdmin.setTenantId(savedTenant.getId());
  78 + tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org");
  79 + tenantAdmin.setFirstName("Joe");
  80 + tenantAdmin.setLastName("Downs");
  81 +
  82 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  83 +
  84 + Device device = new Device();
  85 + device.setName(deviceName);
  86 + device.setType("default");
  87 +
  88 + Device gateway = new Device();
  89 + gateway.setName(gatewayName);
  90 + gateway.setType("default");
  91 + ObjectNode additionalInfo = mapper.createObjectNode();
  92 + additionalInfo.put("gateway", true);
  93 + gateway.setAdditionalInfo(additionalInfo);
  94 +
  95 + if (payloadType != null) {
  96 + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic);
  97 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
  98 + device.setType(savedDeviceProfile.getName());
  99 + device.setDeviceProfileId(savedDeviceProfile.getId());
  100 + gateway.setType(savedDeviceProfile.getName());
  101 + gateway.setDeviceProfileId(savedDeviceProfile.getId());
  102 + }
  103 +
  104 + savedDevice = doPost("/api/device", device, Device.class);
  105 +
  106 + DeviceCredentials deviceCredentials =
  107 + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  108 +
  109 + savedGateway = doPost("/api/device", gateway, Device.class);
  110 +
  111 + DeviceCredentials gatewayCredentials =
  112 + doGet("/api/device/" + savedGateway.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  113 +
  114 + assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
  115 + accessToken = deviceCredentials.getCredentialsId();
  116 + assertNotNull(accessToken);
  117 +
  118 + assertEquals(savedGateway.getId(), gatewayCredentials.getDeviceId());
  119 + gatewayAccessToken = gatewayCredentials.getCredentialsId();
  120 + assertNotNull(gatewayAccessToken);
  121 +
  122 + }
  123 +
  124 + protected void processAfterTest() throws Exception {
  125 + loginSysAdmin();
  126 + if (savedTenant != null) {
  127 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
  128 + }
  129 + }
  130 +
  131 + protected MqttAsyncClient getMqttAsyncClient(String accessToken) throws MqttException {
  132 + String clientId = MqttAsyncClient.generateClientId();
  133 + MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
  134 +
  135 + MqttConnectOptions options = new MqttConnectOptions();
  136 + options.setUserName(accessToken);
  137 + client.connect(options).waitForCompletion();
  138 + return client;
  139 + }
  140 +
  141 + protected void publishMqttMsg(MqttAsyncClient client, byte[] payload, String topic) throws MqttException {
  142 + MqttMessage message = new MqttMessage();
  143 + message.setPayload(payload);
  144 + client.publish(topic, message);
  145 + }
  146 +
  147 + protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
  148 + List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
  149 + TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
  150 + TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
  151 + TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "3.0", TransportProtos.KeyValueType.DOUBLE_V);
  152 + TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "4", TransportProtos.KeyValueType.LONG_V);
  153 + TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
  154 + keyValueProtos.add(strKeyValueProto);
  155 + keyValueProtos.add(boolKeyValueProto);
  156 + keyValueProtos.add(dblKeyValueProto);
  157 + keyValueProtos.add(longKeyValueProto);
  158 + keyValueProtos.add(jsonKeyValueProto);
  159 + return keyValueProtos;
  160 + }
  161 +
  162 + protected TransportProtos.KeyValueProto getKeyValueProto(String key, String strValue, TransportProtos.KeyValueType type) {
  163 + TransportProtos.KeyValueProto.Builder keyValueProtoBuilder = TransportProtos.KeyValueProto.newBuilder();
  164 + keyValueProtoBuilder.setKey(key);
  165 + keyValueProtoBuilder.setType(type);
  166 + switch (type) {
  167 + case BOOLEAN_V:
  168 + keyValueProtoBuilder.setBoolV(Boolean.parseBoolean(strValue));
  169 + break;
  170 + case LONG_V:
  171 + keyValueProtoBuilder.setLongV(Long.parseLong(strValue));
  172 + break;
  173 + case DOUBLE_V:
  174 + keyValueProtoBuilder.setDoubleV(Double.parseDouble(strValue));
  175 + break;
  176 + case STRING_V:
  177 + keyValueProtoBuilder.setStringV(strValue);
  178 + break;
  179 + case JSON_V:
  180 + keyValueProtoBuilder.setJsonV(strValue);
  181 + break;
  182 + }
  183 + return keyValueProtoBuilder.build();
  184 + }
  185 +
  186 + protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic) {
  187 + DeviceProfile deviceProfile = new DeviceProfile();
  188 + deviceProfile.setName(transportPayloadType.name());
  189 + deviceProfile.setType(DeviceProfileType.DEFAULT);
  190 + deviceProfile.setTransportType(DeviceTransportType.MQTT);
  191 + deviceProfile.setDescription(transportPayloadType.name() + " Test");
  192 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  193 + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
  194 + MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration();
  195 + transportConfiguration.setTransportPayloadType(transportPayloadType);
  196 + if (!StringUtils.isEmpty(telemetryTopic)) {
  197 + transportConfiguration.setDeviceTelemetryTopic(telemetryTopic);
  198 + }
  199 + if (!StringUtils.isEmpty(attributesTopic)) {
  200 + transportConfiguration.setDeviceAttributesTopic(attributesTopic);
  201 + }
  202 + deviceProfileData.setTransportConfiguration(transportConfiguration);
  203 + deviceProfileData.setConfiguration(configuration);
  204 + deviceProfile.setProfileData(deviceProfileData);
  205 + deviceProfile.setDefault(false);
  206 + deviceProfile.setDefaultRuleChainId(null);
  207 + return deviceProfile;
  208 + }
  209 +
  210 + protected TransportProtos.PostAttributeMsg getPostAttributeMsg(List<String> expectedKeys) {
  211 + List<TransportProtos.KeyValueProto> kvProtos = getKvProtos(expectedKeys);
  212 + TransportProtos.PostAttributeMsg.Builder builder = TransportProtos.PostAttributeMsg.newBuilder();
  213 + builder.addAllKv(kvProtos);
  214 + return builder.build();
  215 + }
  216 +
  217 + protected <T> T doExecuteWithRetriesAndInterval(SupplierWithThrowable<T> supplier, int retries, int intervalMs) throws Exception {
  218 + int count = 0;
  219 + T result = null;
  220 + Throwable lastException = null;
  221 + while (count < retries) {
  222 + try {
  223 + result = supplier.get();
  224 + if (result != null) {
  225 + return result;
  226 + }
  227 + } catch (Throwable e) {
  228 + lastException = e;
  229 + }
  230 + count++;
  231 + if (count < retries) {
  232 + Thread.sleep(intervalMs);
  233 + }
  234 + }
  235 + if (lastException != null) {
  236 + throw new RuntimeException(lastException);
  237 + } else {
  238 + return result;
  239 + }
  240 + }
  241 +
  242 + @FunctionalInterface
  243 + public interface SupplierWithThrowable<T> {
  244 + T get() throws Throwable;
  245 + }
  246 +}
@@ -33,7 +33,7 @@ public class MqttNoSqlTestSuite { @@ -33,7 +33,7 @@ public class MqttNoSqlTestSuite {
33 33
34 @ClassRule 34 @ClassRule
35 public static CustomSqlUnit sqlUnit = new CustomSqlUnit( 35 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
36 - Arrays.asList("sql/schema-entities-hsql.sql", "sql/system-data.sql"), 36 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
37 "sql/hsql/drop-all-tables.sql", 37 "sql/hsql/drop-all-tables.sql",
38 "nosql-test.properties"); 38 "nosql-test.properties");
39 39
@@ -27,13 +27,17 @@ import java.util.Arrays; @@ -27,13 +27,17 @@ import java.util.Arrays;
27 @RunWith(ClasspathSuite.class) 27 @RunWith(ClasspathSuite.class)
28 @ClasspathSuite.ClassnameFilters({ 28 @ClasspathSuite.ClassnameFilters({
29 "org.thingsboard.server.mqtt.rpc.sql.*Test", 29 "org.thingsboard.server.mqtt.rpc.sql.*Test",
30 - "org.thingsboard.server.mqtt.telemetry.sql.*Test" 30 + "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test",
  31 + "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",
  32 + "org.thingsboard.server.mqtt.attributes.updates.sql.*Test",
  33 + "org.thingsboard.server.mqtt.attributes.request.sql.*Test",
  34 + "org.thingsboard.server.mqtt.claim.sql.*Test"
31 }) 35 })
32 public class MqttSqlTestSuite { 36 public class MqttSqlTestSuite {
33 37
34 @ClassRule 38 @ClassRule
35 public static CustomSqlUnit sqlUnit = new CustomSqlUnit( 39 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
36 - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), 40 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
37 "sql/hsql/drop-all-tables.sql", 41 "sql/hsql/drop-all-tables.sql",
38 "sql-test.properties"); 42 "sql-test.properties");
39 43
  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.mqtt.attributes;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  20 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  21 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  22 +import org.thingsboard.server.common.data.TransportPayloadType;
  23 +import org.thingsboard.server.gen.transport.TransportProtos;
  24 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
  25 +
  26 +import java.util.ArrayList;
  27 +import java.util.List;
  28 +import java.util.concurrent.CountDownLatch;
  29 +
  30 +@Slf4j
  31 +public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
  32 +
  33 + protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," +
  34 + "\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
  35 +
  36 + protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
  37 + super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
  38 + }
  39 +
  40 + protected void processAfterTest() throws Exception {
  41 + super.processAfterTest();
  42 + }
  43 +
  44 + protected List<TransportProtos.TsKvProto> getTsKvProtoList() {
  45 + TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V);
  46 + TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V);
  47 + TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "42.0", TransportProtos.KeyValueType.DOUBLE_V);
  48 + TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "73", TransportProtos.KeyValueType.LONG_V);
  49 + TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V);
  50 + List<TransportProtos.TsKvProto> tsKvProtoList = new ArrayList<>();
  51 + tsKvProtoList.add(tsKvProtoAttribute1);
  52 + tsKvProtoList.add(tsKvProtoAttribute2);
  53 + tsKvProtoList.add(tsKvProtoAttribute3);
  54 + tsKvProtoList.add(tsKvProtoAttribute4);
  55 + tsKvProtoList.add(tsKvProtoAttribute5);
  56 + return tsKvProtoList;
  57 + }
  58 +
  59 +
  60 + protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) {
  61 + TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder();
  62 + TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType);
  63 + tsKvProtoBuilder.setKv(keyValueProto);
  64 + return tsKvProtoBuilder.build();
  65 + }
  66 +
  67 + protected TestMqttCallback getTestMqttCallback() {
  68 + CountDownLatch latch = new CountDownLatch(1);
  69 + return new TestMqttCallback(latch);
  70 + }
  71 +
  72 + protected static class TestMqttCallback implements MqttCallback {
  73 +
  74 + private final CountDownLatch latch;
  75 + private Integer qoS;
  76 + private byte[] payloadBytes;
  77 +
  78 + TestMqttCallback(CountDownLatch latch) {
  79 + this.latch = latch;
  80 + }
  81 +
  82 + public int getQoS() {
  83 + return qoS;
  84 + }
  85 +
  86 + public byte[] getPayloadBytes() {
  87 + return payloadBytes;
  88 + }
  89 +
  90 + public CountDownLatch getLatch() {
  91 + return latch;
  92 + }
  93 +
  94 + @Override
  95 + public void connectionLost(Throwable throwable) {
  96 + }
  97 +
  98 + @Override
  99 + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
  100 + qoS = mqttMessage.getQos();
  101 + payloadBytes = mqttMessage.getPayload();
  102 + latch.countDown();
  103 + }
  104 +
  105 + @Override
  106 + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
  107 +
  108 + }
  109 + }
  110 +
  111 +}
  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.mqtt.attributes.request;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import io.netty.handler.codec.mqtt.MqttQoS;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  22 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  23 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  24 +import org.eclipse.paho.client.mqttv3.MqttException;
  25 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  26 +import org.junit.After;
  27 +import org.junit.Before;
  28 +import org.junit.Test;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  31 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  32 +import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
  33 +
  34 +import java.nio.charset.StandardCharsets;
  35 +import java.util.concurrent.CountDownLatch;
  36 +import java.util.concurrent.TimeUnit;
  37 +
  38 +import static org.junit.Assert.assertEquals;
  39 +import static org.junit.Assert.assertFalse;
  40 +import static org.junit.Assert.assertNotNull;
  41 +import static org.junit.Assert.assertTrue;
  42 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  43 +
  44 +@Slf4j
  45 +public abstract class AbstractMqttAttributesRequestIntegrationTest extends AbstractMqttAttributesIntegrationTest {
  46 +
  47 + @Before
  48 + public void beforeTest() throws Exception {
  49 + processBeforeTest("Test Request attribute values from the server", "Gateway Test Request attribute values from the server", null, null, null);
  50 + }
  51 +
  52 + @After
  53 + public void afterTest() throws Exception {
  54 + processAfterTest();
  55 + }
  56 +
  57 + @Test
  58 + public void testRequestAttributesValuesFromTheServer() throws Exception {
  59 + processTestRequestAttributesValuesFromTheServer();
  60 + }
  61 +
  62 + @Test
  63 + public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
  64 + processTestGatewayRequestAttributesValuesFromTheServer();
  65 + }
  66 +
  67 + protected void processTestRequestAttributesValuesFromTheServer() throws Exception {
  68 +
  69 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  70 +
  71 + postAttributesAndSubscribeToTopic(savedDevice, client);
  72 +
  73 + Thread.sleep(1000);
  74 +
  75 + TestMqttCallback callback = getTestMqttCallback();
  76 + client.setCallback(callback);
  77 +
  78 + validateResponse(client, callback.getLatch(), callback);
  79 + }
  80 +
  81 + protected void processTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
  82 +
  83 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  84 +
  85 + postGatewayDeviceClientAttributes(client);
  86 +
  87 + Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class),
  88 + 20,
  89 + 100);
  90 +
  91 + assertNotNull(savedDevice);
  92 +
  93 + Thread.sleep(1000);
  94 +
  95 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
  96 +
  97 + Thread.sleep(1000);
  98 +
  99 + client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value());
  100 +
  101 + TestMqttCallback clientAttributesCallback = getTestMqttCallback();
  102 + client.setCallback(clientAttributesCallback);
  103 + validateClientResponseGateway(client, clientAttributesCallback);
  104 +
  105 + TestMqttCallback sharedAttributesCallback = getTestMqttCallback();
  106 + client.setCallback(sharedAttributesCallback);
  107 + validateSharedResponseGateway(client, sharedAttributesCallback);
  108 + }
  109 +
  110 + protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception {
  111 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
  112 + client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(POST_ATTRIBUTES_PAYLOAD.getBytes()));
  113 + client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
  114 + }
  115 +
  116 + protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
  117 + String postClientAttributes = "{\"" + "Gateway Device Request Attributes" + "\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  118 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(postClientAttributes.getBytes()));
  119 + }
  120 +
  121 + protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  122 + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
  123 + String payloadStr = "{\"clientKeys\":\"" + keys + "\", \"sharedKeys\":\"" + keys + "\"}";
  124 + MqttMessage mqttMessage = new MqttMessage();
  125 + mqttMessage.setPayload(payloadStr.getBytes());
  126 + client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
  127 + latch.await(3, TimeUnit.SECONDS);
  128 + assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
  129 + String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  130 + assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
  131 + }
  132 +
  133 + protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  134 + String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": true, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
  135 + MqttMessage mqttMessage = new MqttMessage();
  136 + mqttMessage.setPayload(payloadStr.getBytes());
  137 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
  138 + callback.getLatch().await(3, TimeUnit.SECONDS);
  139 + assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
  140 + String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  141 + assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
  142 + }
  143 +
  144 + protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  145 + String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": false, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
  146 + MqttMessage mqttMessage = new MqttMessage();
  147 + mqttMessage.setPayload(payloadStr.getBytes());
  148 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
  149 + callback.getLatch().await(3, TimeUnit.SECONDS);
  150 + assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
  151 + String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  152 + assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
  153 + }
  154 +}
  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.mqtt.attributes.request;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.junit.After;
  20 +import org.junit.Before;
  21 +import org.junit.Ignore;
  22 +import org.junit.Test;
  23 +import org.thingsboard.server.common.data.TransportPayloadType;
  24 +
  25 +import static org.junit.Assert.assertEquals;
  26 +import static org.junit.Assert.assertNotNull;
  27 +import static org.junit.Assert.assertTrue;
  28 +
  29 +@Slf4j
  30 +public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
  31 +
  32 + @Before
  33 + public void beforeTest() throws Exception {
  34 + processBeforeTest("Test Request attribute values from the server json", "Gateway Test Request attribute values from the server json", TransportPayloadType.JSON, null, null);
  35 + }
  36 +
  37 + @After
  38 + public void afterTest() throws Exception {
  39 + processAfterTest();
  40 + }
  41 +
  42 + @Test
  43 + public void testRequestAttributesValuesFromTheServer() throws Exception {
  44 + processTestRequestAttributesValuesFromTheServer();
  45 + }
  46 +
  47 + @Test
  48 + public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
  49 + processTestGatewayRequestAttributesValuesFromTheServer();
  50 + }
  51 +}
  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.mqtt.attributes.request;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import io.netty.handler.codec.mqtt.MqttQoS;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  22 +import org.eclipse.paho.client.mqttv3.MqttException;
  23 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  24 +import org.junit.After;
  25 +import org.junit.Before;
  26 +import org.junit.Test;
  27 +import org.thingsboard.server.common.data.Device;
  28 +import org.thingsboard.server.common.data.TransportPayloadType;
  29 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  30 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  31 +import org.thingsboard.server.gen.transport.TransportProtos;
  32 +
  33 +import java.util.ArrayList;
  34 +import java.util.Arrays;
  35 +import java.util.List;
  36 +import java.util.concurrent.CountDownLatch;
  37 +import java.util.concurrent.TimeUnit;
  38 +import java.util.stream.Collectors;
  39 +
  40 +import static org.junit.Assert.assertEquals;
  41 +import static org.junit.Assert.assertTrue;
  42 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  43 +
  44 +@Slf4j
  45 +public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
  46 +
  47 + @Before
  48 + public void beforeTest() throws Exception {
  49 + processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null);
  50 + }
  51 +
  52 + @After
  53 + public void afterTest() throws Exception {
  54 + processAfterTest();
  55 + }
  56 +
  57 + @Test
  58 + public void testRequestAttributesValuesFromTheServer() throws Exception {
  59 + processTestRequestAttributesValuesFromTheServer();
  60 + }
  61 +
  62 +
  63 + @Test
  64 + public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
  65 + processTestGatewayRequestAttributesValuesFromTheServer();
  66 + }
  67 +
  68 + protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception {
  69 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
  70 + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
  71 + List<String> expectedKeys = Arrays.asList(keys.split(","));
  72 + TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
  73 + byte[] payload = postAttributeMsg.toByteArray();
  74 + client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload));
  75 + client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
  76 + }
  77 +
  78 + protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
  79 + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
  80 + List<String> expectedKeys = Arrays.asList(keys.split(","));
  81 + TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
  82 + TransportApiProtos.AttributesMsg.Builder attributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
  83 + attributesMsgBuilder.setDeviceName("Gateway Device Request Attributes");
  84 + attributesMsgBuilder.setMsg(postAttributeMsg);
  85 + TransportApiProtos.AttributesMsg attributesMsg = attributesMsgBuilder.build();
  86 + TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributeMsgBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
  87 + gatewayAttributeMsgBuilder.addMsg(attributesMsg);
  88 + byte[] bytes = gatewayAttributeMsgBuilder.build().toByteArray();
  89 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(bytes));
  90 + }
  91 +
  92 + protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  93 + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
  94 + TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder();
  95 + attributesRequestBuilder.setClientKeys(keys);
  96 + attributesRequestBuilder.setSharedKeys(keys);
  97 + TransportApiProtos.AttributesRequest attributesRequest = attributesRequestBuilder.build();
  98 + MqttMessage mqttMessage = new MqttMessage();
  99 + mqttMessage.setPayload(attributesRequest.toByteArray());
  100 + client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
  101 + latch.await(3, TimeUnit.SECONDS);
  102 + assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
  103 + TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg();
  104 + TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
  105 + assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId());
  106 + List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  107 + List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  108 + List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  109 + List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  110 + assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
  111 + assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
  112 + }
  113 +
  114 + protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  115 + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
  116 + TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, true);
  117 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
  118 + callback.getLatch().await(3, TimeUnit.SECONDS);
  119 + assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
  120 + TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(true);
  121 + TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
  122 + assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
  123 +
  124 + TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
  125 + TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
  126 + assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
  127 +
  128 + List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  129 + List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  130 + assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
  131 + }
  132 +
  133 + protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  134 + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
  135 + TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, false);
  136 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
  137 + callback.getLatch().await(3, TimeUnit.SECONDS);
  138 + assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
  139 + TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(false);
  140 + TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
  141 + assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
  142 +
  143 + TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
  144 + TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
  145 + assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
  146 +
  147 + List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  148 + List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  149 +
  150 + assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
  151 + }
  152 +
  153 + private TransportApiProtos.GatewayAttributesRequestMsg getGatewayAttributesRequestMsg(String keys, boolean client) {
  154 + return TransportApiProtos.GatewayAttributesRequestMsg.newBuilder()
  155 + .setClient(client)
  156 + .addAllKeys(Arrays.asList(keys.split(",")))
  157 + .setDeviceName("Gateway Device Request Attributes")
  158 + .setId(1).build();
  159 + }
  160 +
  161 + private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() {
  162 + TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder();
  163 + List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
  164 + result.addAllClientAttributeList(tsKvProtoList);
  165 + result.addAllSharedAttributeList(tsKvProtoList);
  166 + result.setRequestId(1);
  167 + return result.build();
  168 + }
  169 +
  170 + private TransportApiProtos.GatewayAttributeResponseMsg getExpectedGatewayAttributeResponseMsg(boolean client) {
  171 + TransportApiProtos.GatewayAttributeResponseMsg.Builder gatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
  172 + TransportProtos.GetAttributeResponseMsg.Builder getAttributeResponseMsgBuilder = TransportProtos.GetAttributeResponseMsg.newBuilder();
  173 + List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
  174 + if (client) {
  175 + getAttributeResponseMsgBuilder.addAllClientAttributeList(tsKvProtoList);
  176 + } else {
  177 + getAttributeResponseMsgBuilder.addAllSharedAttributeList(tsKvProtoList);
  178 + }
  179 + getAttributeResponseMsgBuilder.setRequestId(1);
  180 + TransportProtos.GetAttributeResponseMsg getAttributeResponseMsg = getAttributeResponseMsgBuilder.build();
  181 + gatewayAttributeResponseMsg.setDeviceName("Gateway Device Request Attributes");
  182 + gatewayAttributeResponseMsg.setResponseMsg(getAttributeResponseMsg);
  183 + return gatewayAttributeResponseMsg.build();
  184 + }
  185 +
  186 + protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
  187 + List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
  188 + TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
  189 + TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
  190 + TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "42.0", TransportProtos.KeyValueType.DOUBLE_V);
  191 + TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "73", TransportProtos.KeyValueType.LONG_V);
  192 + TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
  193 + keyValueProtos.add(strKeyValueProto);
  194 + keyValueProtos.add(boolKeyValueProto);
  195 + keyValueProtos.add(dblKeyValueProto);
  196 + keyValueProtos.add(longKeyValueProto);
  197 + keyValueProtos.add(jsonKeyValueProto);
  198 + return keyValueProtos;
  199 + }
  200 +
  201 +}
  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.mqtt.attributes.request.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
  20 +
  21 +
  22 +@DaoNoSqlTest
  23 +public class MqttAttributesRequestNoSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
  24 +}
  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.mqtt.attributes.request.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
  20 +import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest;
  21 +
  22 +@DaoSqlTest
  23 +public class MqttAttributesRequestJsonSqlIntegrationTest extends AbstractMqttAttributesRequestJsonIntegrationTest {
  24 +}
  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.mqtt.attributes.request.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest;
  20 +import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest;
  21 +
  22 +@DaoSqlTest
  23 +public class MqttAttributesRequestProtoSqlIntegrationTest extends AbstractMqttAttributesRequestProtoIntegrationTest {
  24 +}
  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.mqtt.attributes.request.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttAttributesRequestSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
  23 +}
  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.mqtt.attributes.updates;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import io.netty.handler.codec.mqtt.MqttQoS;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  22 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  23 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  24 +import org.eclipse.paho.client.mqttv3.MqttException;
  25 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  26 +import org.junit.After;
  27 +import org.junit.Before;
  28 +import org.junit.Test;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.TransportPayloadType;
  31 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  32 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  33 +import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
  34 +
  35 +import java.nio.charset.StandardCharsets;
  36 +import java.util.concurrent.CountDownLatch;
  37 +import java.util.concurrent.TimeUnit;
  38 +
  39 +import static org.junit.Assert.assertEquals;
  40 +import static org.junit.Assert.assertNotNull;
  41 +import static org.junit.Assert.assertTrue;
  42 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  43 +
  44 +@Slf4j
  45 +public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest {
  46 +
  47 + private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}";
  48 +
  49 + private static String getResponseGatewayAttributesUpdatedPayload() {
  50 + return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\"," +
  51 + "\"data\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  52 + }
  53 +
  54 + private static String getResponseGatewayAttributesDeletedPayload() {
  55 + return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\",\"data\":{\"deleted\":[\"attribute5\"]}}";
  56 + }
  57 +
  58 + @Before
  59 + public void beforeTest() throws Exception {
  60 + processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null);
  61 + }
  62 +
  63 + @After
  64 + public void afterTest() throws Exception {
  65 + processAfterTest();
  66 + }
  67 +
  68 + @Test
  69 + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
  70 + processTestSubscribeToAttributesUpdates();
  71 + }
  72 +
  73 + @Test
  74 + public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
  75 + processGatewayTestSubscribeToAttributesUpdates();
  76 + }
  77 +
  78 + protected void processTestSubscribeToAttributesUpdates() throws Exception {
  79 +
  80 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  81 +
  82 + TestMqttCallback onUpdateCallback = getTestMqttCallback();
  83 + client.setCallback(onUpdateCallback);
  84 +
  85 + client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
  86 +
  87 + Thread.sleep(1000);
  88 +
  89 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
  90 + onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
  91 +
  92 + validateUpdateAttributesResponse(onUpdateCallback);
  93 +
  94 + TestMqttCallback onDeleteCallback = getTestMqttCallback();
  95 + client.setCallback(onDeleteCallback);
  96 +
  97 + doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
  98 + onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
  99 +
  100 + validateDeleteAttributesResponse(onDeleteCallback);
  101 + }
  102 +
  103 + protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  104 + assertNotNull(callback.getPayloadBytes());
  105 + String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
  106 + assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
  107 + }
  108 +
  109 + protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  110 + assertNotNull(callback.getPayloadBytes());
  111 + String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
  112 + assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
  113 + }
  114 +
  115 + protected void processGatewayTestSubscribeToAttributesUpdates() throws Exception {
  116 +
  117 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  118 +
  119 + TestMqttCallback onUpdateCallback = getTestMqttCallback();
  120 + client.setCallback(onUpdateCallback);
  121 +
  122 + Device device = new Device();
  123 + device.setName("Gateway Device Subscribe to attribute updates");
  124 + device.setType("default");
  125 +
  126 + byte[] connectPayloadBytes = getConnectPayloadBytes();
  127 +
  128 + publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
  129 +
  130 + Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class),
  131 + 20,
  132 + 100);
  133 +
  134 + assertNotNull(savedDevice);
  135 +
  136 + client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
  137 +
  138 + Thread.sleep(1000);
  139 +
  140 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
  141 + onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
  142 +
  143 + validateGatewayUpdateAttributesResponse(onUpdateCallback);
  144 +
  145 + TestMqttCallback onDeleteCallback = getTestMqttCallback();
  146 + client.setCallback(onDeleteCallback);
  147 +
  148 + doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
  149 + onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
  150 +
  151 + validateGatewayDeleteAttributesResponse(onDeleteCallback);
  152 +
  153 + }
  154 +
  155 + protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  156 + assertNotNull(callback.getPayloadBytes());
  157 + String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
  158 + assertEquals(getResponseGatewayAttributesUpdatedPayload(), s);
  159 + }
  160 +
  161 + protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  162 + assertNotNull(callback.getPayloadBytes());
  163 + String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
  164 + assertEquals(s, getResponseGatewayAttributesDeletedPayload());
  165 + }
  166 +
  167 + protected byte[] getConnectPayloadBytes() {
  168 + String connectPayload = "{\"device\": \"Gateway Device Subscribe to attribute updates\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
  169 + return connectPayload.getBytes();
  170 + }
  171 +}
  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.mqtt.attributes.updates;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.junit.After;
  20 +import org.junit.Before;
  21 +import org.junit.Test;
  22 +import org.thingsboard.server.common.data.TransportPayloadType;
  23 +
  24 +import static org.junit.Assert.assertEquals;
  25 +import static org.junit.Assert.assertFalse;
  26 +import static org.junit.Assert.assertNotNull;
  27 +import static org.junit.Assert.assertTrue;
  28 +
  29 +@Slf4j
  30 +public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
  31 +
  32 + @Before
  33 + public void beforeTest() throws Exception {
  34 + processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null);
  35 + }
  36 +
  37 + @After
  38 + public void afterTest() throws Exception {
  39 + processAfterTest();
  40 + }
  41 +
  42 + @Test
  43 + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
  44 + processTestSubscribeToAttributesUpdates();
  45 + }
  46 +
  47 + @Test
  48 + public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
  49 + processGatewayTestSubscribeToAttributesUpdates();
  50 + }
  51 +}
  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.mqtt.attributes.updates;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.junit.After;
  21 +import org.junit.Before;
  22 +import org.junit.Test;
  23 +import org.thingsboard.server.common.data.TransportPayloadType;
  24 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  25 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  26 +import org.thingsboard.server.gen.transport.TransportProtos;
  27 +
  28 +import java.nio.charset.StandardCharsets;
  29 +import java.util.List;
  30 +import java.util.stream.Collectors;
  31 +
  32 +import static org.junit.Assert.assertEquals;
  33 +import static org.junit.Assert.assertNotNull;
  34 +import static org.junit.Assert.assertTrue;
  35 +
  36 +@Slf4j
  37 +public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
  38 +
  39 + @Before
  40 + public void beforeTest() throws Exception {
  41 + processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null);
  42 + }
  43 +
  44 + @After
  45 + public void afterTest() throws Exception {
  46 + processAfterTest();
  47 + }
  48 +
  49 + @Test
  50 + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
  51 + processTestSubscribeToAttributesUpdates();
  52 + }
  53 +
  54 + @Test
  55 + public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
  56 + processGatewayTestSubscribeToAttributesUpdates();
  57 + }
  58 +
  59 + protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  60 + assertNotNull(callback.getPayloadBytes());
  61 + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
  62 + List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
  63 + attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
  64 +
  65 + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
  66 + TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
  67 +
  68 + List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  69 + List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  70 +
  71 + assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
  72 + assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
  73 +
  74 + }
  75 +
  76 + protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  77 + assertNotNull(callback.getPayloadBytes());
  78 + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
  79 + attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
  80 +
  81 + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
  82 + TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
  83 +
  84 + assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
  85 + assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
  86 +
  87 + }
  88 +
  89 + protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  90 + assertNotNull(callback.getPayloadBytes());
  91 +
  92 + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
  93 + List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
  94 + attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
  95 + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
  96 +
  97 + TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
  98 + gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
  99 + gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(expectedAttributeUpdateNotificationMsg);
  100 +
  101 + TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
  102 + TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
  103 +
  104 + assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
  105 +
  106 + List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  107 + List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
  108 +
  109 + assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
  110 + assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
  111 +
  112 + }
  113 +
  114 + protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
  115 + assertNotNull(callback.getPayloadBytes());
  116 + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
  117 + attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
  118 + TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
  119 +
  120 + TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
  121 + gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
  122 + gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(attributeUpdateNotificationMsg);
  123 +
  124 + TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
  125 + TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
  126 +
  127 + assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
  128 +
  129 + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
  130 + TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
  131 +
  132 + assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
  133 + assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
  134 +
  135 + }
  136 +
  137 + protected byte[] getConnectPayloadBytes() {
  138 + TransportApiProtos.ConnectMsg connectProto = getConnectProto();
  139 + return connectProto.toByteArray();
  140 + }
  141 +
  142 + private TransportApiProtos.ConnectMsg getConnectProto() {
  143 + TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
  144 + builder.setDeviceName("Gateway Device Subscribe to attribute updates");
  145 + builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
  146 + return builder.build();
  147 + }
  148 +
  149 +}
  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.mqtt.attributes.updates.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
  20 +
  21 +
  22 +@DaoNoSqlTest
  23 +public class MqttAttributesUpdatesNoSqlIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest {
  24 +}
  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.mqtt.attributes.updates.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttAttributesUpdatesSqlIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
  23 +}
  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.mqtt.attributes.updates.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest;
  20 +import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
  21 +
  22 +@DaoSqlTest
  23 +public class MqttAttributesUpdatesSqlJsonIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest {
  24 +}
  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.mqtt.attributes.updates.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
  20 +import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest;
  21 +
  22 +@DaoSqlTest
  23 +public class MqttAttributesUpdatesSqlProtoIntegrationTest extends AbstractMqttAttributesUpdatesProtoIntegrationTest {
  24 +}
  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.mqtt.claim;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  20 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  21 +import org.junit.After;
  22 +import org.junit.Before;
  23 +import org.junit.Ignore;
  24 +import org.junit.Test;
  25 +import org.thingsboard.server.common.data.ClaimRequest;
  26 +import org.thingsboard.server.common.data.Customer;
  27 +import org.thingsboard.server.common.data.Device;
  28 +import org.thingsboard.server.common.data.User;
  29 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  30 +import org.thingsboard.server.common.data.security.Authority;
  31 +import org.thingsboard.server.dao.device.claim.ClaimResponse;
  32 +import org.thingsboard.server.dao.device.claim.ClaimResult;
  33 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
  34 +
  35 +import static org.junit.Assert.assertEquals;
  36 +import static org.junit.Assert.assertNotNull;
  37 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  38 +
  39 +@Slf4j
  40 +public abstract class AbstractMqttClaimDeviceTest extends AbstractMqttIntegrationTest {
  41 +
  42 + protected static final String CUSTOMER_USER_PASSWORD = "customerUser123!";
  43 +
  44 + protected User customerAdmin;
  45 + protected Customer savedCustomer;
  46 +
  47 + @Before
  48 + public void beforeTest() throws Exception {
  49 + super.processBeforeTest("Test Claim device", "Test Claim gateway", null, null, null);
  50 + createCustomerAndUser();
  51 + }
  52 +
  53 + protected void createCustomerAndUser() throws Exception {
  54 + Customer customer = new Customer();
  55 + customer.setTenantId(savedTenant.getId());
  56 + customer.setTitle("Test Claiming Customer");
  57 + savedCustomer = doPost("/api/customer", customer, Customer.class);
  58 + assertNotNull(savedCustomer);
  59 + assertEquals(savedTenant.getId(), savedCustomer.getTenantId());
  60 +
  61 + User user = new User();
  62 + user.setAuthority(Authority.CUSTOMER_USER);
  63 + user.setTenantId(savedTenant.getId());
  64 + user.setCustomerId(savedCustomer.getId());
  65 + user.setEmail("customer@thingsboard.org");
  66 +
  67 + customerAdmin = createUser(user, CUSTOMER_USER_PASSWORD);
  68 + assertNotNull(customerAdmin);
  69 + assertEquals(customerAdmin.getCustomerId(), savedCustomer.getId());
  70 + }
  71 +
  72 + @After
  73 + public void afterTest() throws Exception {
  74 + super.processAfterTest();
  75 + }
  76 +
  77 + @Test
  78 + public void testClaimingDevice() throws Exception {
  79 + processTestClaimingDevice(false);
  80 + }
  81 +
  82 + @Test
  83 + public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
  84 + processTestClaimingDevice(true);
  85 + }
  86 +
  87 + @Test
  88 + public void testGatewayClaimingDevice() throws Exception {
  89 + processTestGatewayClaimingDevice("Test claiming gateway device", false);
  90 + }
  91 +
  92 + @Test
  93 + public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
  94 + processTestGatewayClaimingDevice("Test claiming gateway device empty payload", true);
  95 + }
  96 +
  97 +
  98 + protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
  99 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  100 + byte[] payloadBytes;
  101 + byte[] failurePayloadBytes;
  102 + if (emptyPayload) {
  103 + payloadBytes = "{}".getBytes();
  104 + failurePayloadBytes = "{\"durationMs\":1}".getBytes();
  105 + } else {
  106 + payloadBytes = "{\"secretKey\":\"value\", \"durationMs\":60000}".getBytes();
  107 + failurePayloadBytes = "{\"secretKey\":\"value\", \"durationMs\":1}".getBytes();
  108 + }
  109 + validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes);
  110 + }
  111 +
  112 + protected void validateClaimResponse(boolean emptyPayload, MqttAsyncClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception {
  113 + client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes));
  114 +
  115 + loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD);
  116 + ClaimRequest claimRequest;
  117 + if (!emptyPayload) {
  118 + claimRequest = new ClaimRequest("value");
  119 + } else {
  120 + claimRequest = new ClaimRequest(null);
  121 + }
  122 +
  123 + ClaimResponse claimResponse = doExecuteWithRetriesAndInterval(
  124 + () -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest()),
  125 + 20,
  126 + 100
  127 + );
  128 +
  129 + assertEquals(claimResponse, ClaimResponse.FAILURE);
  130 +
  131 + client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(payloadBytes));
  132 +
  133 + ClaimResult claimResult = doExecuteWithRetriesAndInterval(
  134 + () -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResult.class, status().isOk()),
  135 + 20,
  136 + 100
  137 + );
  138 + assertEquals(claimResult.getResponse(), ClaimResponse.SUCCESS);
  139 + Device claimedDevice = claimResult.getDevice();
  140 + assertNotNull(claimedDevice);
  141 + assertNotNull(claimedDevice.getCustomerId());
  142 + assertEquals(customerAdmin.getCustomerId(), claimedDevice.getCustomerId());
  143 +
  144 + claimResponse = doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
  145 + assertEquals(claimResponse, ClaimResponse.CLAIMED);
  146 + }
  147 +
  148 + protected void validateGatewayClaimResponse(String deviceName, boolean emptyPayload, MqttAsyncClient client, byte[] failurePayloadBytes, byte[] payloadBytes) throws Exception {
  149 + client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes));
  150 +
  151 + Device savedDevice = doExecuteWithRetriesAndInterval(
  152 + () -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
  153 + 20,
  154 + 100
  155 + );
  156 +
  157 + assertNotNull(savedDevice);
  158 +
  159 + loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD);
  160 + ClaimRequest claimRequest;
  161 + if (!emptyPayload) {
  162 + claimRequest = new ClaimRequest("value");
  163 + } else {
  164 + claimRequest = new ClaimRequest(null);
  165 + }
  166 +
  167 + ClaimResponse claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
  168 + assertEquals(claimResponse, ClaimResponse.FAILURE);
  169 +
  170 + client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(payloadBytes));
  171 +
  172 + ClaimResult claimResult = doExecuteWithRetriesAndInterval(
  173 + () -> doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResult.class, status().isOk()),
  174 + 20,
  175 + 100
  176 + );
  177 +
  178 + assertEquals(claimResult.getResponse(), ClaimResponse.SUCCESS);
  179 + Device claimedDevice = claimResult.getDevice();
  180 + assertNotNull(claimedDevice);
  181 + assertNotNull(claimedDevice.getCustomerId());
  182 + assertEquals(customerAdmin.getCustomerId(), claimedDevice.getCustomerId());
  183 +
  184 + claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
  185 + assertEquals(claimResponse, ClaimResponse.CLAIMED);
  186 + }
  187 +
  188 + protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
  189 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  190 + byte[] failurePayloadBytes;
  191 + byte[] payloadBytes;
  192 + String failurePayload;
  193 + String payload;
  194 + if (emptyPayload) {
  195 + failurePayload = "{\"" + deviceName + "\": " + "{\"durationMs\":1}" + "}";
  196 + payload = "{\"" + deviceName + "\": " + "{}" + "}";
  197 + } else {
  198 + failurePayload = "{\"" + deviceName + "\": " + "{\"secretKey\":\"value\", \"durationMs\":1}" + "}";
  199 + payload = "{\"" + deviceName + "\": " + "{\"secretKey\":\"value\", \"durationMs\":60000}" + "}";
  200 + }
  201 + payloadBytes = payload.getBytes();
  202 + failurePayloadBytes = failurePayload.getBytes();
  203 + validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
  204 + }
  205 +
  206 +}
  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.mqtt.claim;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.junit.After;
  20 +import org.junit.Before;
  21 +import org.junit.Ignore;
  22 +import org.junit.Test;
  23 +import org.thingsboard.server.common.data.TransportPayloadType;
  24 +
  25 +@Slf4j
  26 +public abstract class AbstractMqttClaimJsonDeviceTest extends AbstractMqttClaimDeviceTest {
  27 +
  28 + @Before
  29 + public void beforeTest() throws Exception {
  30 + super.processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.JSON, null, null);
  31 + createCustomerAndUser();
  32 + }
  33 +
  34 + @After
  35 + public void afterTest() throws Exception {
  36 + super.afterTest();
  37 + }
  38 +
  39 + @Test
  40 + public void testClaimingDevice() throws Exception {
  41 + processTestClaimingDevice(false);
  42 + }
  43 +
  44 + @Test
  45 + public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
  46 + processTestClaimingDevice(true);
  47 + }
  48 +
  49 + @Test
  50 + public void testGatewayClaimingDevice() throws Exception {
  51 + processTestGatewayClaimingDevice("Test claiming gateway device Json", false);
  52 + }
  53 +
  54 + @Test
  55 + public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
  56 + processTestGatewayClaimingDevice("Test claiming gateway device empty payload Json", true);
  57 + }
  58 +}
  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.mqtt.claim;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  20 +import org.junit.After;
  21 +import org.junit.Before;
  22 +import org.junit.Ignore;
  23 +import org.junit.Test;
  24 +import org.thingsboard.server.common.data.TransportPayloadType;
  25 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  26 +
  27 +@Slf4j
  28 +public abstract class AbstractMqttClaimProtoDeviceTest extends AbstractMqttClaimDeviceTest {
  29 +
  30 + @Before
  31 + public void beforeTest() throws Exception {
  32 + processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.PROTOBUF, null, null);
  33 + createCustomerAndUser();
  34 + }
  35 +
  36 + @After
  37 + public void afterTest() throws Exception { super.afterTest(); }
  38 +
  39 + @Test
  40 + public void testClaimingDevice() throws Exception {
  41 + processTestClaimingDevice(false);
  42 + }
  43 +
  44 + @Test
  45 + public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
  46 + processTestClaimingDevice(true);
  47 + }
  48 +
  49 + @Test
  50 + public void testGatewayClaimingDevice() throws Exception {
  51 + processTestGatewayClaimingDevice("Test claiming gateway device Proto", false);
  52 + }
  53 +
  54 + @Test
  55 + public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
  56 + processTestGatewayClaimingDevice("Test claiming gateway device empty payload Proto", true);
  57 + }
  58 +
  59 + protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
  60 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  61 + byte[] payloadBytes;
  62 + if (emptyPayload) {
  63 + payloadBytes = getClaimDevice(0, emptyPayload).toByteArray();
  64 + } else {
  65 + payloadBytes = getClaimDevice(60000, emptyPayload).toByteArray();
  66 + }
  67 + byte[] failurePayloadBytes = getClaimDevice(1, emptyPayload).toByteArray();
  68 + validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes);
  69 + }
  70 +
  71 + protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
  72 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  73 + byte[] failurePayloadBytes;
  74 + byte[] payloadBytes;
  75 + if (emptyPayload) {
  76 + payloadBytes = getGatewayClaimMsg(deviceName, 0, emptyPayload).toByteArray();
  77 + } else {
  78 + payloadBytes = getGatewayClaimMsg(deviceName, 60000, emptyPayload).toByteArray();
  79 + }
  80 + failurePayloadBytes = getGatewayClaimMsg(deviceName, 1, emptyPayload).toByteArray();
  81 +
  82 + validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
  83 + }
  84 +
  85 + private TransportApiProtos.GatewayClaimMsg getGatewayClaimMsg(String deviceName, long duration, boolean emptyPayload) {
  86 + TransportApiProtos.GatewayClaimMsg.Builder gatewayClaimMsgBuilder = TransportApiProtos.GatewayClaimMsg.newBuilder();
  87 + TransportApiProtos.ClaimDeviceMsg.Builder claimDeviceMsgBuilder = TransportApiProtos.ClaimDeviceMsg.newBuilder();
  88 + TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
  89 + if (!emptyPayload) {
  90 + claimDeviceBuilder.setSecretKey("value");
  91 + }
  92 + if (duration > 0) {
  93 + claimDeviceBuilder.setDurationMs(duration);
  94 + }
  95 + TransportApiProtos.ClaimDevice claimDevice = claimDeviceBuilder.build();
  96 + claimDeviceMsgBuilder.setClaimRequest(claimDevice);
  97 + claimDeviceMsgBuilder.setDeviceName(deviceName);
  98 + TransportApiProtos.ClaimDeviceMsg claimDeviceMsg = claimDeviceMsgBuilder.build();
  99 + gatewayClaimMsgBuilder.addMsg(claimDeviceMsg);
  100 + return gatewayClaimMsgBuilder.build();
  101 + }
  102 +
  103 + private TransportApiProtos.ClaimDevice getClaimDevice(long duration, boolean emptyPayload) {
  104 + TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
  105 + if (!emptyPayload) {
  106 + claimDeviceBuilder.setSecretKey("value");
  107 + }
  108 + if (duration > 0) {
  109 + claimDeviceBuilder.setSecretKey("value");
  110 + claimDeviceBuilder.setDurationMs(duration);
  111 + }
  112 + return claimDeviceBuilder.build();
  113 + }
  114 +
  115 +
  116 +}
  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.mqtt.claim.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
  20 +
  21 +
  22 +@DaoNoSqlTest
  23 +public class MqttClaimDeviceNoSqlTest extends AbstractMqttClaimDeviceTest {
  24 +}
  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.mqtt.claim.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
  20 +import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest;
  21 +
  22 +@DaoSqlTest
  23 +public class MqttClaimDeviceJsonSqlTest extends AbstractMqttClaimJsonDeviceTest {
  24 +}
  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.mqtt.claim.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest;
  20 +import org.thingsboard.server.mqtt.claim.AbstractMqttClaimProtoDeviceTest;
  21 +
  22 +@DaoSqlTest
  23 +public class MqttClaimDeviceProtoSqlTest extends AbstractMqttClaimProtoDeviceTest {
  24 +}
  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.mqtt.claim.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttClaimDeviceSqlTest extends AbstractMqttClaimDeviceTest {
  23 +}
  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.mqtt.rpc;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.node.ObjectNode;
  20 +import com.google.protobuf.InvalidProtocolBufferException;
  21 +import com.nimbusds.jose.util.StandardCharset;
  22 +import com.datastax.oss.driver.api.core.uuid.Uuids;
  23 +import io.netty.handler.codec.mqtt.MqttQoS;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.apache.commons.lang3.StringUtils;
  26 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  27 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  28 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  29 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  30 +import org.eclipse.paho.client.mqttv3.MqttException;
  31 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  32 +import org.junit.After;
  33 +import org.junit.Assert;
  34 +import org.junit.Before;
  35 +import org.junit.Ignore;
  36 +import org.junit.Test;
  37 +import org.thingsboard.server.common.data.Device;
  38 +import org.thingsboard.server.common.data.DeviceProfile;
  39 +import org.thingsboard.server.common.data.DeviceProfileType;
  40 +import org.thingsboard.server.common.data.DeviceTransportType;
  41 +import org.thingsboard.server.common.data.Tenant;
  42 +import org.thingsboard.server.common.data.TransportPayloadType;
  43 +import org.thingsboard.server.common.data.User;
  44 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  45 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  46 +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
  47 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  48 +import org.thingsboard.server.common.data.security.Authority;
  49 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  50 +import org.thingsboard.server.controller.AbstractControllerTest;
  51 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  52 +import org.thingsboard.server.service.security.AccessValidator;
  53 +
  54 +import java.util.Arrays;
  55 +import java.util.concurrent.CountDownLatch;
  56 +import java.util.concurrent.TimeUnit;
  57 +import java.util.concurrent.atomic.AtomicInteger;
  58 +
  59 +import static org.junit.Assert.assertEquals;
  60 +import static org.junit.Assert.assertNotNull;
  61 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  62 +
  63 +/**
  64 + * @author Valerii Sosliuk
  65 + */
  66 +@Slf4j
  67 +public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
  68 +
  69 + @Before
  70 + public void beforeTest() throws Exception {
  71 + processBeforeTest("RPC test device", "RPC test gateway", null, null, null);
  72 + }
  73 +
  74 + @After
  75 + public void afterTest() throws Exception {
  76 + super.processAfterTest();
  77 + }
  78 +
  79 + @Test
  80 + public void testServerMqttOneWayRpcDeviceOffline() throws Exception {
  81 + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
  82 + String deviceId = savedDevice.getId().getId().toString();
  83 +
  84 + doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409),
  85 + asyncContextTimeoutToUseRpcPlugin);
  86 + }
  87 +
  88 + @Test
  89 + public void testServerMqttOneWayRpcDeviceDoesNotExist() throws Exception {
  90 + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";
  91 + String nonExistentDeviceId = Uuids.timeBased().toString();
  92 +
  93 + String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class,
  94 + status().isNotFound());
  95 + Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
  96 + }
  97 +
  98 + @Test
  99 + public void testServerMqttTwoWayRpcDeviceOffline() throws Exception {
  100 + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
  101 + String deviceId = savedDevice.getId().getId().toString();
  102 +
  103 + doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409),
  104 + asyncContextTimeoutToUseRpcPlugin);
  105 + }
  106 +
  107 + @Test
  108 + public void testServerMqttTwoWayRpcDeviceDoesNotExist() throws Exception {
  109 + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";
  110 + String nonExistentDeviceId = Uuids.timeBased().toString();
  111 +
  112 + String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,
  113 + status().isNotFound());
  114 + Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
  115 + }
  116 +
  117 + @Test
  118 + public void testServerMqttOneWayRpc() throws Exception {
  119 + processOneWayRpcTest();
  120 + }
  121 +
  122 + @Test
  123 + public void testServerMqttTwoWayRpc() throws Exception {
  124 + processTwoWayRpcTest();
  125 + }
  126 +
  127 + @Test
  128 + public void testGatewayServerMqttOneWayRpc() throws Exception {
  129 + processOneWayRpcTestGateway("Gateway Device OneWay RPC");
  130 + }
  131 +
  132 + @Test
  133 + public void testGatewayServerMqttTwoWayRpc() throws Exception {
  134 + processTwoWayRpcTestGateway("Gateway Device TwoWay RPC");
  135 + }
  136 +
  137 +}
@@ -16,6 +16,10 @@ @@ -16,6 +16,10 @@
16 package org.thingsboard.server.mqtt.rpc; 16 package org.thingsboard.server.mqtt.rpc;
17 17
18 import com.datastax.oss.driver.api.core.uuid.Uuids; 18 import com.datastax.oss.driver.api.core.uuid.Uuids;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
  21 +import com.google.protobuf.InvalidProtocolBufferException;
  22 +import com.nimbusds.jose.util.StandardCharset;
19 import io.netty.handler.codec.mqtt.MqttQoS; 23 import io.netty.handler.codec.mqtt.MqttQoS;
20 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
21 import org.apache.commons.lang3.StringUtils; 25 import org.apache.commons.lang3.StringUtils;
@@ -23,15 +27,28 @@ import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; @@ -23,15 +27,28 @@ import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
23 import org.eclipse.paho.client.mqttv3.MqttAsyncClient; 27 import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
24 import org.eclipse.paho.client.mqttv3.MqttCallback; 28 import org.eclipse.paho.client.mqttv3.MqttCallback;
25 import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 29 import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  30 +import org.eclipse.paho.client.mqttv3.MqttException;
26 import org.eclipse.paho.client.mqttv3.MqttMessage; 31 import org.eclipse.paho.client.mqttv3.MqttMessage;
27 -import org.junit.*; 32 +import org.junit.After;
  33 +import org.junit.Assert;
  34 +import org.junit.Before;
  35 +import org.junit.Test;
28 import org.thingsboard.server.common.data.Device; 36 import org.thingsboard.server.common.data.Device;
  37 +import org.thingsboard.server.common.data.DeviceProfile;
  38 +import org.thingsboard.server.common.data.DeviceProfileType;
  39 +import org.thingsboard.server.common.data.DeviceTransportType;
29 import org.thingsboard.server.common.data.Tenant; 40 import org.thingsboard.server.common.data.Tenant;
  41 +import org.thingsboard.server.common.data.TransportPayloadType;
30 import org.thingsboard.server.common.data.User; 42 import org.thingsboard.server.common.data.User;
  43 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  44 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  45 +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
  46 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
31 import org.thingsboard.server.common.data.security.Authority; 47 import org.thingsboard.server.common.data.security.Authority;
32 import org.thingsboard.server.common.data.security.DeviceCredentials; 48 import org.thingsboard.server.common.data.security.DeviceCredentials;
33 import org.thingsboard.server.controller.AbstractControllerTest; 49 import org.thingsboard.server.controller.AbstractControllerTest;
34 -import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest; 50 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  51 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
35 import org.thingsboard.server.service.security.AccessValidator; 52 import org.thingsboard.server.service.security.AccessValidator;
36 53
37 import java.util.Arrays; 54 import java.util.Arrays;
@@ -47,72 +64,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -47,72 +64,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
47 * @author Valerii Sosliuk 64 * @author Valerii Sosliuk
48 */ 65 */
49 @Slf4j 66 @Slf4j
50 -public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractControllerTest { 67 +public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest {
51 68
52 - private static final String MQTT_URL = "tcp://localhost:1883";  
53 - private static final Long TIME_TO_HANDLE_REQUEST = 500L; 69 + protected static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
54 70
55 - private Tenant savedTenant;  
56 - private User tenantAdmin;  
57 - private Long asyncContextTimeoutToUseRpcPlugin;  
58 -  
59 - private static final AtomicInteger atomicInteger = new AtomicInteger(2);  
60 -  
61 -  
62 - @Before  
63 - public void beforeTest() throws Exception {  
64 - loginSysAdmin(); 71 + protected Long asyncContextTimeoutToUseRpcPlugin;
65 72
  73 + protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
  74 + super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
66 asyncContextTimeoutToUseRpcPlugin = 10000L; 75 asyncContextTimeoutToUseRpcPlugin = 10000L;
67 -  
68 - Tenant tenant = new Tenant();  
69 - tenant.setTitle("My tenant");  
70 - savedTenant = doPost("/api/tenant", tenant, Tenant.class);  
71 - Assert.assertNotNull(savedTenant);  
72 -  
73 - tenantAdmin = new User();  
74 - tenantAdmin.setAuthority(Authority.TENANT_ADMIN);  
75 - tenantAdmin.setTenantId(savedTenant.getId());  
76 - tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org");  
77 - tenantAdmin.setFirstName("Joe");  
78 - tenantAdmin.setLastName("Downs");  
79 -  
80 - createUserAndLogin(tenantAdmin, "testPassword1");  
81 } 76 }
82 77
83 - @After  
84 - public void afterTest() throws Exception {  
85 - loginSysAdmin();  
86 - if (savedTenant != null) {  
87 - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());  
88 - }  
89 - }  
90 -  
91 - @Test  
92 - public void testServerMqttOneWayRpc() throws Exception {  
93 - Device device = new Device();  
94 - device.setName("Test One-Way Server-Side RPC");  
95 - device.setType("default");  
96 - Device savedDevice = getSavedDevice(device);  
97 - DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);  
98 - assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());  
99 - String accessToken = deviceCredentials.getCredentialsId();  
100 - assertNotNull(accessToken);  
101 -  
102 - String clientId = MqttAsyncClient.generateClientId();  
103 - MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);  
104 -  
105 - MqttConnectOptions options = new MqttConnectOptions();  
106 - options.setUserName(accessToken);  
107 - client.connect(options).waitForCompletion(); 78 + protected void processOneWayRpcTest() throws Exception {
  79 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
108 80
109 CountDownLatch latch = new CountDownLatch(1); 81 CountDownLatch latch = new CountDownLatch(1);
110 TestMqttCallback callback = new TestMqttCallback(client, latch); 82 TestMqttCallback callback = new TestMqttCallback(client, latch);
111 client.setCallback(callback); 83 client.setCallback(callback);
112 84
113 - client.subscribe("v1/devices/me/rpc/request/+", MqttQoS.AT_MOST_ONCE.value()); 85 + client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_MOST_ONCE.value());
114 86
115 - Thread.sleep(2000); 87 + Thread.sleep(1000);
116 88
117 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; 89 String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
118 String deviceId = savedDevice.getId().getId().toString(); 90 String deviceId = savedDevice.getId().getId().toString();
@@ -122,100 +94,112 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC @@ -122,100 +94,112 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
122 assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); 94 assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
123 } 95 }
124 96
125 - @Test  
126 - public void testServerMqttOneWayRpcDeviceOffline() throws Exception {  
127 - Device device = new Device();  
128 - device.setName("Test One-Way Server-Side RPC Device Offline");  
129 - device.setType("default");  
130 - Device savedDevice = getSavedDevice(device);  
131 - DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);  
132 - assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());  
133 - String accessToken = deviceCredentials.getCredentialsId();  
134 - assertNotNull(accessToken);  
135 -  
136 - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}"; 97 + protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
  98 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  99 + String payload = "{\"device\":\"" + deviceName + "\"}";
  100 + byte[] payloadBytes = payload.getBytes();
  101 + validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
  102 + }
  103 +
  104 + protected void processTwoWayRpcTest() throws Exception {
  105 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  106 + client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, 1);
  107 +
  108 + CountDownLatch latch = new CountDownLatch(1);
  109 + TestMqttCallback callback = new TestMqttCallback(client, latch);
  110 + client.setCallback(callback);
  111 +
  112 + Thread.sleep(1000);
  113 +
  114 + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
137 String deviceId = savedDevice.getId().getId().toString(); 115 String deviceId = savedDevice.getId().getId().toString();
138 116
139 - doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409),  
140 - asyncContextTimeoutToUseRpcPlugin); 117 + String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
  118 + String expected = "{\"value1\":\"A\",\"value2\":\"B\"}";
  119 + latch.await(3, TimeUnit.SECONDS);
  120 + Assert.assertEquals(expected, result);
141 } 121 }
142 122
143 - @Test  
144 - public void testServerMqttOneWayRpcDeviceDoesNotExist() throws Exception {  
145 - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";  
146 - String nonExistentDeviceId = Uuids.timeBased().toString(); 123 + protected void processTwoWayRpcTestGateway(String deviceName) throws Exception {
  124 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  125 +
  126 + String payload = "{\"device\":\"" + deviceName + "\"}";
  127 + byte[] payloadBytes = payload.getBytes();
147 128
148 - String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class,  
149 - status().isNotFound());  
150 - Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); 129 + validateTwoWayRpcGateway(deviceName, client, payloadBytes);
151 } 130 }
152 131
153 - @Test  
154 - public void testServerMqttTwoWayRpc() throws Exception {  
155 - Device device = new Device();  
156 - device.setName("Test Two-Way Server-Side RPC");  
157 - device.setType("default");  
158 - Device savedDevice = getSavedDevice(device);  
159 - DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);  
160 - assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());  
161 - String accessToken = deviceCredentials.getCredentialsId();  
162 - assertNotNull(accessToken); 132 + protected void validateOneWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
  133 + publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
163 134
164 - String clientId = MqttAsyncClient.generateClientId();  
165 - MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId); 135 + Device savedDevice = doExecuteWithRetriesAndInterval(
  136 + () -> getDeviceByName(deviceName),
  137 + 20,
  138 + 100
  139 + );
  140 + assertNotNull(savedDevice);
166 141
167 - MqttConnectOptions options = new MqttConnectOptions();  
168 - options.setUserName(accessToken);  
169 - client.connect(options).waitForCompletion();  
170 - client.subscribe("v1/devices/me/rpc/request/+", 1);  
171 - client.setCallback(new TestMqttCallback(client, new CountDownLatch(1))); 142 + CountDownLatch latch = new CountDownLatch(1);
  143 + TestMqttCallback callback = new TestMqttCallback(client, latch);
  144 + client.setCallback(callback);
172 145
173 - Thread.sleep(2000); 146 + client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
174 147
175 - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";  
176 - String deviceId = savedDevice.getId().getId().toString(); 148 + Thread.sleep(1000);
177 149
178 - String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());  
179 - Assert.assertEquals("{\"value1\":\"A\",\"value2\":\"B\"}", result); 150 + String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
  151 + String deviceId = savedDevice.getId().getId().toString();
  152 + String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
  153 + Assert.assertTrue(StringUtils.isEmpty(result));
  154 + latch.await(3, TimeUnit.SECONDS);
  155 + assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
180 } 156 }
181 157
182 - @Test  
183 - public void testServerMqttTwoWayRpcDeviceOffline() throws Exception {  
184 - Device device = new Device();  
185 - device.setName("Test Two-Way Server-Side RPC Device Offline");  
186 - device.setType("default");  
187 - Device savedDevice = getSavedDevice(device);  
188 - DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);  
189 - assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());  
190 - String accessToken = deviceCredentials.getCredentialsId();  
191 - assertNotNull(accessToken);  
192 -  
193 - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";  
194 - String deviceId = savedDevice.getId().getId().toString(); 158 + protected void validateTwoWayRpcGateway(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
  159 + publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
195 160
196 - doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409),  
197 - asyncContextTimeoutToUseRpcPlugin);  
198 - } 161 + Device savedDevice = doExecuteWithRetriesAndInterval(
  162 + () -> getDeviceByName(deviceName),
  163 + 20,
  164 + 100
  165 + );
  166 + assertNotNull(savedDevice);
  167 +
  168 + CountDownLatch latch = new CountDownLatch(1);
  169 + TestMqttCallback callback = new TestMqttCallback(client, latch);
  170 + client.setCallback(callback);
  171 +
  172 + client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
199 173
200 - @Test  
201 - public void testServerMqttTwoWayRpcDeviceDoesNotExist() throws Exception {  
202 - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";  
203 - String nonExistentDeviceId = Uuids.timeBased().toString(); 174 + Thread.sleep(1000);
204 175
205 - String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,  
206 - status().isNotFound());  
207 - Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); 176 + String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
  177 + String deviceId = savedDevice.getId().getId().toString();
  178 + String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
  179 + latch.await(3, TimeUnit.SECONDS);
  180 + String expected = "{\"success\":true}";
  181 + assertEquals(expected, result);
  182 + assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
208 } 183 }
209 184
210 - private Device getSavedDevice(Device device) throws Exception {  
211 - return doPost("/api/device", device, Device.class); 185 + private Device getDeviceByName(String deviceName) throws Exception {
  186 + return doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
212 } 187 }
213 188
214 - private DeviceCredentials getDeviceCredentials(Device savedDevice) throws Exception {  
215 - return doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); 189 + protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
  190 + MqttMessage message = new MqttMessage();
  191 + if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) {
  192 + message.setPayload(DEVICE_RESPONSE.getBytes(StandardCharset.UTF_8));
  193 + } else {
  194 + JsonNode requestMsgNode = JacksonUtil.toJsonNode(new String(mqttMessage.getPayload(), StandardCharset.UTF_8));
  195 + String deviceName = requestMsgNode.get("device").asText();
  196 + int requestId = requestMsgNode.get("data").get("id").asInt();
  197 + message.setPayload(("{\"device\": \"" + deviceName + "\", \"id\": " + requestId + ", \"data\": {\"success\": true}}").getBytes(StandardCharset.UTF_8));
  198 + }
  199 + return message;
216 } 200 }
217 201
218 - private static class TestMqttCallback implements MqttCallback { 202 + private class TestMqttCallback implements MqttCallback {
219 203
220 private final MqttAsyncClient client; 204 private final MqttAsyncClient client;
221 private final CountDownLatch latch; 205 private final CountDownLatch latch;
@@ -237,11 +221,9 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC @@ -237,11 +221,9 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
237 @Override 221 @Override
238 public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { 222 public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
239 log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload())); 223 log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload()));
240 - MqttMessage message = new MqttMessage();  
241 String responseTopic = requestTopic.replace("request", "response"); 224 String responseTopic = requestTopic.replace("request", "response");
242 - message.setPayload("{\"value1\":\"A\", \"value2\":\"B\"}".getBytes("UTF-8"));  
243 qoS = mqttMessage.getQos(); 225 qoS = mqttMessage.getQos();
244 - client.publish(responseTopic, message); 226 + client.publish(responseTopic, processMessageArrived(requestTopic, mqttMessage));
245 latch.countDown(); 227 latch.countDown();
246 } 228 }
247 229
  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.mqtt.rpc;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  20 +import org.junit.After;
  21 +import org.junit.Before;
  22 +import org.junit.Ignore;
  23 +import org.junit.Test;
  24 +import org.thingsboard.server.common.data.TransportPayloadType;
  25 +
  26 +@Slf4j
  27 +public abstract class AbstractMqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
  28 +
  29 + @Before
  30 + public void beforeTest() throws Exception {
  31 + processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.JSON, null, null);
  32 + }
  33 +
  34 + @After
  35 + public void afterTest() throws Exception {
  36 + super.processAfterTest();
  37 + }
  38 +
  39 + @Test
  40 + public void testServerMqttOneWayRpc() throws Exception {
  41 + processOneWayRpcTest();
  42 + }
  43 +
  44 + @Test
  45 + public void testServerMqttTwoWayRpc() throws Exception {
  46 + processTwoWayRpcTest();
  47 + }
  48 +
  49 + @Test
  50 + public void testGatewayServerMqttOneWayRpc() throws Exception {
  51 + processOneWayRpcTestGateway("Gateway Device OneWay RPC Json");
  52 + }
  53 +
  54 + @Test
  55 + public void testGatewayServerMqttTwoWayRpc() throws Exception {
  56 + processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Json");
  57 + }
  58 +
  59 + protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
  60 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  61 + String payload = "{\"device\": \"" + deviceName + "\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
  62 + byte[] payloadBytes = payload.getBytes();
  63 + validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
  64 + }
  65 +
  66 +}
  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.mqtt.rpc;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  21 +import org.eclipse.paho.client.mqttv3.MqttException;
  22 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  23 +import org.junit.After;
  24 +import org.junit.Before;
  25 +import org.junit.Ignore;
  26 +import org.junit.Test;
  27 +import org.thingsboard.server.common.data.TransportPayloadType;
  28 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  29 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  30 +import org.thingsboard.server.gen.transport.TransportProtos;
  31 +
  32 +import static org.junit.Assert.assertEquals;
  33 +import static org.junit.Assert.assertNotNull;
  34 +
  35 +@Slf4j
  36 +public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
  37 +
  38 + @Before
  39 + public void beforeTest() throws Exception {
  40 + processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null);
  41 + }
  42 +
  43 + @After
  44 + public void afterTest() throws Exception {
  45 + super.processAfterTest();
  46 + }
  47 +
  48 + @Test
  49 + public void testServerMqttOneWayRpc() throws Exception {
  50 + processOneWayRpcTest();
  51 + }
  52 +
  53 + @Test
  54 + public void testServerMqttTwoWayRpc() throws Exception {
  55 + processTwoWayRpcTest();
  56 + }
  57 +
  58 + @Test
  59 + public void testGatewayServerMqttOneWayRpc() throws Exception {
  60 + processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto");
  61 + }
  62 +
  63 + @Test
  64 + public void testGatewayServerMqttTwoWayRpc() throws Exception {
  65 + processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto");
  66 + }
  67 +
  68 + protected void processTwoWayRpcTestGateway(String deviceName) throws Exception {
  69 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  70 + TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
  71 + byte[] payloadBytes = connectMsgProto.toByteArray();
  72 + validateTwoWayRpcGateway(deviceName, client, payloadBytes);
  73 + }
  74 +
  75 + protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
  76 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  77 + TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
  78 + byte[] payloadBytes = connectMsgProto.toByteArray();
  79 + validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
  80 + }
  81 +
  82 +
  83 + private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
  84 + TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
  85 + builder.setDeviceName(deviceName);
  86 + builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
  87 + return builder.build();
  88 + }
  89 +
  90 + protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
  91 + MqttMessage message = new MqttMessage();
  92 + if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) {
  93 + TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
  94 + .setPayload(DEVICE_RESPONSE)
  95 + .setRequestId(0)
  96 + .build();
  97 + message.setPayload(toDeviceRpcResponseMsg.toByteArray());
  98 + } else {
  99 + TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload());
  100 + String deviceName = msg.getDeviceName();
  101 + int requestId = msg.getRpcRequestMsg().getRequestId();
  102 + TransportApiProtos.GatewayRpcResponseMsg gatewayRpcResponseMsg = TransportApiProtos.GatewayRpcResponseMsg.newBuilder()
  103 + .setDeviceName(deviceName)
  104 + .setId(requestId)
  105 + .setData("{\"success\": true}")
  106 + .build();
  107 + message.setPayload(gatewayRpcResponseMsg.toByteArray());
  108 + }
  109 + return message;
  110 + }
  111 +
  112 +
  113 +
  114 +}
@@ -16,11 +16,11 @@ @@ -16,11 +16,11 @@
16 package org.thingsboard.server.mqtt.rpc.nosql; 16 package org.thingsboard.server.mqtt.rpc.nosql;
17 17
18 import org.thingsboard.server.dao.service.DaoNoSqlTest; 18 import org.thingsboard.server.dao.service.DaoNoSqlTest;
19 -import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest; 19 +import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest;
20 20
21 /** 21 /**
22 * Created by Valerii Sosliuk on 8/22/2017. 22 * Created by Valerii Sosliuk on 8/22/2017.
23 */ 23 */
24 @DaoNoSqlTest 24 @DaoNoSqlTest
25 -public class MqttServerSideRpcNoSqlIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { 25 +public class MqttServerSideRpcNoSqlIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest {
26 } 26 }
  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.mqtt.rpc.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttServerSideRpcJsonSqlIntegrationTest extends AbstractMqttServerSideRpcJsonIntegrationTest {
  23 +}
  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.mqtt.rpc.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcProtoIntegrationTest;
  20 +
  21 +
  22 +@DaoSqlTest
  23 +public class MqttServerSideRpcProtoSqlIntegrationTest extends AbstractMqttServerSideRpcProtoIntegrationTest {
  24 +}
@@ -16,11 +16,11 @@ @@ -16,11 +16,11 @@
16 package org.thingsboard.server.mqtt.rpc.sql; 16 package org.thingsboard.server.mqtt.rpc.sql;
17 17
18 import org.thingsboard.server.dao.service.DaoSqlTest; 18 import org.thingsboard.server.dao.service.DaoSqlTest;
19 -import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest; 19 +import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest;
20 20
21 /** 21 /**
22 * Created by Valerii Sosliuk on 8/22/2017. 22 * Created by Valerii Sosliuk on 8/22/2017.
23 */ 23 */
24 @DaoSqlTest 24 @DaoSqlTest
25 -public class MqttServerSideRpcSqlIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { 25 +public class MqttServerSideRpcSqlIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest {
26 } 26 }
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.mqtt.telemetry;  
17 -  
18 -import io.netty.handler.codec.mqtt.MqttQoS;  
19 -import lombok.extern.slf4j.Slf4j;  
20 -import org.eclipse.paho.client.mqttv3.*;  
21 -import org.junit.Before;  
22 -import org.junit.Ignore;  
23 -import org.junit.Test;  
24 -import org.springframework.web.util.UriComponentsBuilder;  
25 -import org.thingsboard.server.common.data.Device;  
26 -import org.thingsboard.server.common.data.security.DeviceCredentials;  
27 -import org.thingsboard.server.controller.AbstractControllerTest;  
28 -import org.thingsboard.server.dao.service.DaoNoSqlTest;  
29 -  
30 -import java.net.URI;  
31 -import java.util.*;  
32 -import java.util.concurrent.CountDownLatch;  
33 -import java.util.concurrent.TimeUnit;  
34 -  
35 -import static org.junit.Assert.assertEquals;  
36 -import static org.junit.Assert.assertNotNull;  
37 -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;  
38 -  
39 -/**  
40 - * @author Valerii Sosliuk  
41 - */  
42 -@Slf4j  
43 -public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractControllerTest {  
44 -  
45 - private static final String MQTT_URL = "tcp://localhost:1883";  
46 -  
47 - private Device savedDevice;  
48 - private String accessToken;  
49 -  
50 - @Before  
51 - public void beforeTest() throws Exception {  
52 - loginTenantAdmin();  
53 -  
54 - Device device = new Device();  
55 - device.setName("Test device");  
56 - device.setType("default");  
57 - savedDevice = doPost("/api/device", device, Device.class);  
58 -  
59 - DeviceCredentials deviceCredentials =  
60 - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);  
61 -  
62 - assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());  
63 - accessToken = deviceCredentials.getCredentialsId();  
64 - assertNotNull(accessToken);  
65 - }  
66 -  
67 - @Test  
68 - public void testPushMqttRpcData() throws Exception {  
69 - String clientId = MqttAsyncClient.generateClientId();  
70 - MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);  
71 -  
72 - MqttConnectOptions options = new MqttConnectOptions();  
73 - options.setUserName(accessToken);  
74 - client.connect(options);  
75 - Thread.sleep(3000);  
76 - MqttMessage message = new MqttMessage();  
77 - message.setPayload("{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4}".getBytes());  
78 - client.publish("v1/devices/me/telemetry", message);  
79 -  
80 - String deviceId = savedDevice.getId().getId().toString();  
81 -  
82 - Thread.sleep(2000);  
83 - List<String> actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", List.class);  
84 - Set<String> actualKeySet = new HashSet<>(actualKeys);  
85 -  
86 - List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4");  
87 - Set<String> expectedKeySet = new HashSet<>(expectedKeys);  
88 -  
89 - assertEquals(expectedKeySet, actualKeySet);  
90 -  
91 - String getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet);  
92 - Map<String, List<Map<String, String>>> values = doGetAsync(getTelemetryValuesUrl, Map.class);  
93 -  
94 - assertEquals("value1", values.get("key1").get(0).get("value"));  
95 - assertEquals("true", values.get("key2").get(0).get("value"));  
96 - assertEquals("3.0", values.get("key3").get(0).get("value"));  
97 - assertEquals("4", values.get("key4").get(0).get("value"));  
98 - }  
99 -  
100 -  
101 -// @Test - Unstable  
102 - public void testMqttQoSLevel() throws Exception {  
103 - String clientId = MqttAsyncClient.generateClientId();  
104 - MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);  
105 -  
106 - MqttConnectOptions options = new MqttConnectOptions();  
107 - options.setUserName(accessToken);  
108 - CountDownLatch latch = new CountDownLatch(1);  
109 - TestMqttCallback callback = new TestMqttCallback(client, latch);  
110 - client.setCallback(callback);  
111 - client.connect(options).waitForCompletion(5000);  
112 - client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());  
113 - String payload = "{\"key\":\"uniqueValue\"}";  
114 -// TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue.  
115 -// MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed)  
116 -// MqttClient <- SUB_ACK <- Transport  
117 - Thread.sleep(5000);  
118 - doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk());  
119 - latch.await(10, TimeUnit.SECONDS);  
120 - assertEquals(payload, callback.getPayload());  
121 - assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());  
122 - }  
123 -  
124 - private static class TestMqttCallback implements MqttCallback {  
125 -  
126 - private final MqttAsyncClient client;  
127 - private final CountDownLatch latch;  
128 - private volatile Integer qoS;  
129 - private volatile String payload;  
130 -  
131 - String getPayload() {  
132 - return payload;  
133 - }  
134 -  
135 - TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) {  
136 - this.client = client;  
137 - this.latch = latch;  
138 - }  
139 -  
140 - int getQoS() {  
141 - return qoS;  
142 - }  
143 -  
144 - @Override  
145 - public void connectionLost(Throwable throwable) {  
146 - log.error("Client connection lost", throwable);  
147 - }  
148 -  
149 - @Override  
150 - public void messageArrived(String requestTopic, MqttMessage mqttMessage) {  
151 - payload = new String(mqttMessage.getPayload());  
152 - qoS = mqttMessage.getQos();  
153 - latch.countDown();  
154 - }  
155 -  
156 - @Override  
157 - public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {  
158 -  
159 - }  
160 - }  
161 -  
162 -  
163 -}  
  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.mqtt.telemetry.attributes;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  20 +import org.junit.After;
  21 +import org.junit.Before;
  22 +import org.junit.Test;
  23 +import org.thingsboard.server.common.data.Device;
  24 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  25 +import org.thingsboard.server.common.data.id.DeviceId;
  26 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
  27 +
  28 +import java.util.Arrays;
  29 +import java.util.HashSet;
  30 +import java.util.LinkedHashMap;
  31 +import java.util.List;
  32 +import java.util.Map;
  33 +import java.util.Set;
  34 +
  35 +import static org.junit.Assert.assertEquals;
  36 +import static org.junit.Assert.assertNotNull;
  37 +import static org.junit.Assert.assertTrue;
  38 +
  39 +@Slf4j
  40 +public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
  41 +
  42 + protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
  43 + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
  44 +
  45 + @Before
  46 + public void beforeTest() throws Exception {
  47 + processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", null, null, null);
  48 + }
  49 +
  50 + @After
  51 + public void afterTest() throws Exception {
  52 + processAfterTest();
  53 + }
  54 +
  55 + @Test
  56 + public void testPushMqttAttributes() throws Exception {
  57 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  58 + processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes());
  59 + }
  60 +
  61 + @Test
  62 + public void testPushMqttAttributesGateway() throws Exception {
  63 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  64 + String deviceName1 = "Device A";
  65 + String deviceName2 = "Device B";
  66 + String payload = getGatewayAttributesJsonPayload(deviceName1, deviceName2);
  67 + processGatewayAttributesTest(expectedKeys, payload.getBytes(), deviceName1, deviceName2);
  68 + }
  69 +
  70 + protected void processAttributesTest(String topic, List<String> expectedKeys, byte[] payload) throws Exception {
  71 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  72 +
  73 + publishMqttMsg(client, payload, topic);
  74 +
  75 + DeviceId deviceId = savedDevice.getId();
  76 +
  77 + long start = System.currentTimeMillis();
  78 + long end = System.currentTimeMillis() + 5000;
  79 +
  80 + List<String> actualKeys = null;
  81 + while (start <= end) {
  82 + actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", List.class);
  83 + if (actualKeys.size() == expectedKeys.size()) {
  84 + break;
  85 + }
  86 + Thread.sleep(100);
  87 + start += 100;
  88 + }
  89 + assertNotNull(actualKeys);
  90 +
  91 + Set<String> actualKeySet = new HashSet<>(actualKeys);
  92 +
  93 + Set<String> expectedKeySet = new HashSet<>(expectedKeys);
  94 +
  95 + assertEquals(expectedKeySet, actualKeySet);
  96 +
  97 + String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet);
  98 + List<Map<String, Object>> values = doGetAsync(getAttributesValuesUrl, List.class);
  99 + assertAttributesValues(values, expectedKeySet);
  100 + String deleteAttributesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
  101 + doDelete(deleteAttributesUrl);
  102 + }
  103 +
  104 + protected void processGatewayAttributesTest(List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception {
  105 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  106 +
  107 + publishMqttMsg(client, payload, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC);
  108 +
  109 + Device firstDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class),
  110 + 20,
  111 + 100);
  112 +
  113 + assertNotNull(firstDevice);
  114 +
  115 + Device secondDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + secondDeviceName, Device.class),
  116 + 20,
  117 + 100);
  118 +
  119 + assertNotNull(secondDevice);
  120 +
  121 + Thread.sleep(2000);
  122 +
  123 + List<String> firstDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/attributes/CLIENT_SCOPE", List.class);
  124 + Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys);
  125 +
  126 + List<String> secondDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/attributes/CLIENT_SCOPE", List.class);
  127 + Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys);
  128 +
  129 + Set<String> expectedKeySet = new HashSet<>(expectedKeys);
  130 +
  131 + assertEquals(expectedKeySet, firstDeviceActualKeySet);
  132 + assertEquals(expectedKeySet, secondDeviceActualKeySet);
  133 +
  134 + String getAttributesValuesUrlFirstDevice = getAttributesValuesUrl(firstDevice.getId(), firstDeviceActualKeySet);
  135 + String getAttributesValuesUrlSecondDevice = getAttributesValuesUrl(firstDevice.getId(), secondDeviceActualKeySet);
  136 +
  137 + List<Map<String, Object>> firstDeviceValues = doGetAsync(getAttributesValuesUrlFirstDevice, List.class);
  138 + List<Map<String, Object>> secondDeviceValues = doGetAsync(getAttributesValuesUrlSecondDevice, List.class);
  139 +
  140 + assertAttributesValues(firstDeviceValues, expectedKeySet);
  141 + assertAttributesValues(secondDeviceValues, expectedKeySet);
  142 +
  143 + }
  144 +
  145 + protected void assertAttributesValues(List<Map<String, Object>> deviceValues, Set<String> expectedKeySet) {
  146 + for (Map<String, Object> map : deviceValues) {
  147 + String key = (String) map.get("key");
  148 + Object value = map.get("value");
  149 + assertTrue(expectedKeySet.contains(key));
  150 + switch (key) {
  151 + case "key1":
  152 + assertEquals("value1", value);
  153 + break;
  154 + case "key2":
  155 + assertEquals(true, value);
  156 + break;
  157 + case "key3":
  158 + assertEquals(3.0, value);
  159 + break;
  160 + case "key4":
  161 + assertEquals(4, value);
  162 + break;
  163 + case "key5":
  164 + assertNotNull(value);
  165 + assertEquals(3, ((LinkedHashMap) value).size());
  166 + assertEquals(42, ((LinkedHashMap) value).get("someNumber"));
  167 + assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray"));
  168 + LinkedHashMap<String, String> someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject");
  169 + assertEquals("value", someNestedObject.get("key"));
  170 + break;
  171 + }
  172 + }
  173 + }
  174 +
  175 + protected String getGatewayAttributesJsonPayload(String deviceA, String deviceB) {
  176 + return "{\"" + deviceA + "\": " + PAYLOAD_VALUES_STR + ", \"" + deviceB + "\": " + PAYLOAD_VALUES_STR + "}";
  177 + }
  178 +
  179 + private String getAttributesValuesUrl(DeviceId deviceId, Set<String> actualKeySet) {
  180 + return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/attributes/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
  181 + }
  182 +}
  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.mqtt.telemetry.attributes;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.junit.After;
  20 +import org.junit.Before;
  21 +import org.junit.Test;
  22 +import org.thingsboard.server.common.data.TransportPayloadType;
  23 +
  24 +import java.util.Arrays;
  25 +import java.util.List;
  26 +
  27 +@Slf4j
  28 +public abstract class AbstractMqttAttributesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest {
  29 +
  30 + private static final String POST_DATA_ATTRIBUTES_TOPIC = "data/attributes";
  31 +
  32 + @Before
  33 + public void beforeTest() throws Exception {
  34 + processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.JSON, null, POST_DATA_ATTRIBUTES_TOPIC);
  35 + }
  36 +
  37 + @After
  38 + public void afterTest() throws Exception {
  39 + processAfterTest();
  40 + }
  41 +
  42 + @Test
  43 + public void testPushMqttAttributes() throws Exception {
  44 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  45 + processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes());
  46 + }
  47 +
  48 + @Test
  49 + public void testPushMqttAttributesGateway() throws Exception {
  50 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  51 + String deviceName1 = "Device A";
  52 + String deviceName2 = "Device B";
  53 + String payload = getGatewayAttributesJsonPayload(deviceName1, deviceName2);
  54 + processGatewayAttributesTest(expectedKeys, payload.getBytes(), deviceName1, deviceName2);
  55 + }
  56 +}
  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.mqtt.telemetry.attributes;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.junit.After;
  20 +import org.junit.Before;
  21 +import org.junit.Test;
  22 +import org.thingsboard.server.common.data.TransportPayloadType;
  23 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  24 +import org.thingsboard.server.gen.transport.TransportProtos;
  25 +
  26 +import java.util.Arrays;
  27 +import java.util.List;
  28 +
  29 +import static org.junit.Assert.assertEquals;
  30 +import static org.junit.Assert.assertNotNull;
  31 +import static org.junit.Assert.assertTrue;
  32 +
  33 +@Slf4j
  34 +public abstract class AbstractMqttAttributesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest {
  35 +
  36 + private static final String POST_DATA_ATTRIBUTES_TOPIC = "proto/attributes";
  37 +
  38 + @Before
  39 + public void beforeTest() throws Exception {
  40 + processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
  41 + }
  42 +
  43 + @After
  44 + public void afterTest() throws Exception {
  45 + processAfterTest();
  46 + }
  47 +
  48 + @Test
  49 + public void testPushMqttAttributes() throws Exception {
  50 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  51 + TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys);
  52 + processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, msg.toByteArray());
  53 + }
  54 +
  55 + @Test
  56 + public void testPushMqttAttributesGateway() throws Exception {
  57 + TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
  58 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  59 + String deviceName1 = "Device A";
  60 + String deviceName2 = "Device B";
  61 + TransportApiProtos.AttributesMsg firstDeviceAttributesMsgProto = getDeviceAttributesMsgProto(deviceName1, expectedKeys);
  62 + TransportApiProtos.AttributesMsg secondDeviceAttributesMsgProto = getDeviceAttributesMsgProto(deviceName2, expectedKeys);
  63 + gatewayAttributesMsgProtoBuilder.addAllMsg(Arrays.asList(firstDeviceAttributesMsgProto, secondDeviceAttributesMsgProto));
  64 + TransportApiProtos.GatewayAttributesMsg gatewayAttributesMsg = gatewayAttributesMsgProtoBuilder.build();
  65 + processGatewayAttributesTest(expectedKeys, gatewayAttributesMsg.toByteArray(), deviceName1, deviceName2);
  66 + }
  67 +
  68 + private TransportApiProtos.AttributesMsg getDeviceAttributesMsgProto(String deviceName, List<String> expectedKeys) {
  69 + TransportApiProtos.AttributesMsg.Builder deviceAttributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
  70 + TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys);
  71 + deviceAttributesMsgBuilder.setDeviceName(deviceName);
  72 + deviceAttributesMsgBuilder.setMsg(msg);
  73 + return deviceAttributesMsgBuilder.build();
  74 + }
  75 +}
  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.mqtt.telemetry.attributes.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
  20 +
  21 +@DaoNoSqlTest
  22 +public class MqttAttributesNoSqlIntegrationTest extends AbstractMqttAttributesIntegrationTest {
  23 +}
  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.mqtt.telemetry.attributes.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
  20 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest;
  21 +
  22 +@DaoNoSqlTest
  23 +public class MqttAttributesNoSqlJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest {
  24 +}
  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.mqtt.telemetry.attributes.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
  20 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest;
  21 +
  22 +@DaoNoSqlTest
  23 +public class MqttAttributesNoSqlProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest {
  24 +}
  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.mqtt.telemetry.attributes.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttAttributesSqlIntegrationTest extends AbstractMqttAttributesIntegrationTest {
  23 +}
  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.mqtt.telemetry.attributes.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttAttributesSqlJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest {
  23 +}
  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.mqtt.telemetry.attributes.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest;
  20 +import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest;
  21 +
  22 +@DaoSqlTest
  23 +public class MqttAttributesSqlProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest {
  24 +}
  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.mqtt.telemetry.timeseries;
  17 +
  18 +import io.netty.handler.codec.mqtt.MqttQoS;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  21 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  22 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  23 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  24 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  25 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
  26 +import org.junit.After;
  27 +import org.junit.Before;
  28 +import org.junit.Test;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.TransportPayloadType;
  31 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  32 +import org.thingsboard.server.common.data.id.DeviceId;
  33 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
  34 +
  35 +import java.util.Arrays;
  36 +import java.util.HashSet;
  37 +import java.util.List;
  38 +import java.util.Map;
  39 +import java.util.Set;
  40 +import java.util.concurrent.CountDownLatch;
  41 +import java.util.concurrent.TimeUnit;
  42 +
  43 +import static org.junit.Assert.assertEquals;
  44 +import static org.junit.Assert.assertNotNull;
  45 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  46 +
  47 +@Slf4j
  48 +public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqttIntegrationTest {
  49 +
  50 + protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
  51 + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
  52 +
  53 + @Before
  54 + public void beforeTest() throws Exception {
  55 + processBeforeTest("Test Post Telemetry device", "Test Post Telemetry gateway", null, null, null);
  56 + }
  57 +
  58 + @After
  59 + public void afterTest() throws Exception {
  60 + processAfterTest();
  61 + }
  62 +
  63 + @Test
  64 + public void testPushMqttTelemetry() throws Exception {
  65 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  66 + processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
  67 + }
  68 +
  69 + @Test
  70 + public void testPushMqttTelemetryWithTs() throws Exception {
  71 + String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}";
  72 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  73 + processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true);
  74 + }
  75 +
  76 + @Test
  77 + public void testPushMqttTelemetryGateway() throws Exception {
  78 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  79 + String deviceName1 = "Device A";
  80 + String deviceName2 = "Device B";
  81 + String payload = getGatewayTelemetryJsonPayload(deviceName1, deviceName2, "10000", "20000");
  82 + processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, payload.getBytes(), deviceName1, deviceName2);
  83 + }
  84 +
  85 + @Test
  86 + public void testGatewayConnect() throws Exception {
  87 + String payload = "{\"device\":\"Device A\"}";
  88 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  89 + publishMqttMsg(client, payload.getBytes(), MqttTopics.GATEWAY_CONNECT_TOPIC);
  90 +
  91 + String deviceName = "Device A";
  92 +
  93 + Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
  94 + 20,
  95 + 100);
  96 +
  97 + assertNotNull(device);
  98 + }
  99 +
  100 + protected void processTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, boolean withTs) throws Exception {
  101 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  102 + publishMqttMsg(client, payload, topic);
  103 +
  104 + String deviceId = savedDevice.getId().getId().toString();
  105 +
  106 + long start = System.currentTimeMillis();
  107 + long end = System.currentTimeMillis() + 5000;
  108 +
  109 + List<String> actualKeys = null;
  110 + while (start <= end) {
  111 + actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", List.class);
  112 + if (actualKeys.size() == expectedKeys.size()) {
  113 + break;
  114 + }
  115 + Thread.sleep(100);
  116 + start += 100;
  117 + }
  118 + assertNotNull(actualKeys);
  119 +
  120 + Set<String> actualKeySet = new HashSet<>(actualKeys);
  121 + Set<String> expectedKeySet = new HashSet<>(expectedKeys);
  122 +
  123 + assertEquals(expectedKeySet, actualKeySet);
  124 +
  125 + String getTelemetryValuesUrl;
  126 + if (withTs) {
  127 + getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?startTs=0&endTs=15000&keys=" + String.join(",", actualKeySet);
  128 + } else {
  129 + getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet);
  130 + }
  131 + Map<String, List<Map<String, String>>> values = doGetAsync(getTelemetryValuesUrl, Map.class);
  132 +
  133 + if (withTs) {
  134 + assertTs(values, expectedKeys, 10000, 0);
  135 + }
  136 + assertValues(values, 0);
  137 + }
  138 +
  139 + protected void processGatewayTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception {
  140 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  141 +
  142 + publishMqttMsg(client, payload, topic);
  143 +
  144 + Device firstDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class),
  145 + 20,
  146 + 100);
  147 +
  148 + assertNotNull(firstDevice);
  149 +
  150 + Device secondDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + secondDeviceName, Device.class),
  151 + 20,
  152 + 100);
  153 +
  154 + assertNotNull(secondDevice);
  155 +
  156 + Thread.sleep(2000);
  157 +
  158 + List<String> firstDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/timeseries", List.class);
  159 + Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys);
  160 +
  161 + List<String> secondDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/timeseries", List.class);
  162 + Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys);
  163 +
  164 + Set<String> expectedKeySet = new HashSet<>(expectedKeys);
  165 +
  166 + assertEquals(expectedKeySet, firstDeviceActualKeySet);
  167 + assertEquals(expectedKeySet, secondDeviceActualKeySet);
  168 +
  169 + String getTelemetryValuesUrlFirstDevice = getTelemetryValuesUrl(firstDevice.getId(), firstDeviceActualKeySet);
  170 + String getTelemetryValuesUrlSecondDevice = getTelemetryValuesUrl(firstDevice.getId(), secondDeviceActualKeySet);
  171 +
  172 + Map<String, List<Map<String, String>>> firstDeviceValues = doGetAsync(getTelemetryValuesUrlFirstDevice, Map.class);
  173 + Map<String, List<Map<String, String>>> secondDeviceValues = doGetAsync(getTelemetryValuesUrlSecondDevice, Map.class);
  174 +
  175 + assertGatewayDeviceData(firstDeviceValues, expectedKeys);
  176 + assertGatewayDeviceData(secondDeviceValues, expectedKeys);
  177 + }
  178 +
  179 + protected String getGatewayTelemetryJsonPayload(String deviceA, String deviceB, String firstTsValue, String secondTsValue) {
  180 + String payload = "[{\"ts\": " + firstTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}, " +
  181 + "{\"ts\": " + secondTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}]";
  182 + return "{\"" + deviceA + "\": " + payload + ", \"" + deviceB + "\": " + payload + "}";
  183 + }
  184 +
  185 + private String getTelemetryValuesUrl(DeviceId deviceId, Set<String> actualKeySet) {
  186 + return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?startTs=0&endTs=25000&keys=" + String.join(",", actualKeySet);
  187 + }
  188 +
  189 + private void assertGatewayDeviceData(Map<String, List<Map<String, String>>> deviceValues, List<String> expectedKeys) {
  190 +
  191 + assertEquals(2, deviceValues.get(expectedKeys.get(0)).size());
  192 + assertEquals(2, deviceValues.get(expectedKeys.get(1)).size());
  193 + assertEquals(2, deviceValues.get(expectedKeys.get(2)).size());
  194 + assertEquals(2, deviceValues.get(expectedKeys.get(3)).size());
  195 + assertEquals(2, deviceValues.get(expectedKeys.get(4)).size());
  196 +
  197 + assertTs(deviceValues, expectedKeys, 20000, 0);
  198 + assertTs(deviceValues, expectedKeys, 10000, 1);
  199 +
  200 + assertValues(deviceValues, 0);
  201 + assertValues(deviceValues, 1);
  202 +
  203 + }
  204 +
  205 + private void assertValues(Map<String, List<Map<String, String>>> deviceValues, int arrayIndex) {
  206 + for (Map.Entry<String, List<Map<String, String>>> entry : deviceValues.entrySet()) {
  207 + String key = entry.getKey();
  208 + List<Map<String, String>> tsKv = entry.getValue();
  209 + String value = tsKv.get(arrayIndex).get("value");
  210 + switch (key) {
  211 + case "key1":
  212 + assertEquals("value1", value);
  213 + break;
  214 + case "key2":
  215 + assertEquals("true", value);
  216 + break;
  217 + case "key3":
  218 + assertEquals("3.0", value);
  219 + break;
  220 + case "key4":
  221 + assertEquals("4", value);
  222 + break;
  223 + case "key5":
  224 + assertEquals("{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", value);
  225 + break;
  226 + }
  227 + }
  228 + }
  229 +
  230 + private void assertTs(Map<String, List<Map<String, String>>> deviceValues, List<String> expectedKeys, int ts, int arrayIndex) {
  231 + assertEquals(ts, deviceValues.get(expectedKeys.get(0)).get(arrayIndex).get("ts"));
  232 + assertEquals(ts, deviceValues.get(expectedKeys.get(1)).get(arrayIndex).get("ts"));
  233 + assertEquals(ts, deviceValues.get(expectedKeys.get(2)).get(arrayIndex).get("ts"));
  234 + assertEquals(ts, deviceValues.get(expectedKeys.get(3)).get(arrayIndex).get("ts"));
  235 + assertEquals(ts, deviceValues.get(expectedKeys.get(4)).get(arrayIndex).get("ts"));
  236 + }
  237 +
  238 + // @Test - Unstable
  239 + public void testMqttQoSLevel() throws Exception {
  240 + String clientId = MqttAsyncClient.generateClientId();
  241 + MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
  242 +
  243 + MqttConnectOptions options = new MqttConnectOptions();
  244 + options.setUserName(accessToken);
  245 + CountDownLatch latch = new CountDownLatch(1);
  246 + TestMqttCallback callback = new TestMqttCallback(client, latch);
  247 + client.setCallback(callback);
  248 + client.connect(options).waitForCompletion(5000);
  249 + client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
  250 + String payload = "{\"key\":\"uniqueValue\"}";
  251 +// TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue.
  252 +// MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed)
  253 +// MqttClient <- SUB_ACK <- Transport
  254 + Thread.sleep(5000);
  255 + doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk());
  256 + latch.await(10, TimeUnit.SECONDS);
  257 + assertEquals(payload, callback.getPayload());
  258 + assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
  259 + }
  260 +
  261 + private static class TestMqttCallback implements MqttCallback {
  262 +
  263 + private final MqttAsyncClient client;
  264 + private final CountDownLatch latch;
  265 + private volatile Integer qoS;
  266 + private volatile String payload;
  267 +
  268 + String getPayload() {
  269 + return payload;
  270 + }
  271 +
  272 + TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
  273 + this.client = client;
  274 + this.latch = latch;
  275 + }
  276 +
  277 + int getQoS() {
  278 + return qoS;
  279 + }
  280 +
  281 + @Override
  282 + public void connectionLost(Throwable throwable) {
  283 + log.error("Client connection lost", throwable);
  284 + }
  285 +
  286 + @Override
  287 + public void messageArrived(String requestTopic, MqttMessage mqttMessage) {
  288 + payload = new String(mqttMessage.getPayload());
  289 + qoS = mqttMessage.getQos();
  290 + latch.countDown();
  291 + }
  292 +
  293 + @Override
  294 + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
  295 +
  296 + }
  297 + }
  298 +
  299 +
  300 +}
  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.mqtt.telemetry.timeseries;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  20 +import org.junit.After;
  21 +import org.junit.Before;
  22 +import org.junit.Test;
  23 +import org.thingsboard.server.common.data.Device;
  24 +import org.thingsboard.server.common.data.TransportPayloadType;
  25 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  26 +
  27 +import java.util.Arrays;
  28 +import java.util.List;
  29 +
  30 +import static org.junit.Assert.assertEquals;
  31 +import static org.junit.Assert.assertNotNull;
  32 +
  33 +@Slf4j
  34 +public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
  35 +
  36 + private static final String POST_DATA_TELEMETRY_TOPIC = "data/telemetry";
  37 +
  38 + @Before
  39 + public void beforeTest() throws Exception {
  40 + processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
  41 + }
  42 +
  43 + @After
  44 + public void afterTest() throws Exception {
  45 + processAfterTest();
  46 + }
  47 +
  48 + @Test
  49 + public void testPushMqttTelemetry() throws Exception {
  50 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  51 + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
  52 + }
  53 +
  54 + @Test
  55 + public void testPushMqttTelemetryWithTs() throws Exception {
  56 + String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}";
  57 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  58 + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true);
  59 + }
  60 +
  61 + @Test
  62 + public void testPushMqttTelemetryGateway() throws Exception {
  63 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  64 + String deviceName1 = "Device A";
  65 + String deviceName2 = "Device B";
  66 + String payload = getGatewayTelemetryJsonPayload(deviceName1, deviceName2, "10000", "20000");
  67 + processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, payload.getBytes(), deviceName1, deviceName2);
  68 + }
  69 +
  70 + @Test
  71 + public void testGatewayConnect() throws Exception {
  72 + String payload = "{\"device\":\"Device A\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
  73 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  74 + publishMqttMsg(client, payload.getBytes(), MqttTopics.GATEWAY_CONNECT_TOPIC);
  75 +
  76 + String deviceName = "Device A";
  77 + Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
  78 + 20,
  79 + 100);
  80 + assertNotNull(device);
  81 + }
  82 +}
  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.mqtt.telemetry.timeseries;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  20 +import org.junit.After;
  21 +import org.junit.Before;
  22 +import org.junit.Test;
  23 +import org.thingsboard.server.common.data.Device;
  24 +import org.thingsboard.server.common.data.TransportPayloadType;
  25 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  26 +import org.thingsboard.server.gen.transport.TransportApiProtos;
  27 +import org.thingsboard.server.gen.transport.TransportProtos;
  28 +
  29 +import java.util.Arrays;
  30 +import java.util.List;
  31 +
  32 +import static org.junit.Assert.assertEquals;
  33 +import static org.junit.Assert.assertNotNull;
  34 +
  35 +@Slf4j
  36 +public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
  37 +
  38 + private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry";
  39 +
  40 + @Before
  41 + public void beforeTest() throws Exception {
  42 + processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
  43 + }
  44 +
  45 + @After
  46 + public void afterTest() throws Exception {
  47 + processAfterTest();
  48 + }
  49 +
  50 + @Test
  51 + public void testPushMqttTelemetry() throws Exception {
  52 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  53 + TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 0);
  54 + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), false);
  55 + }
  56 +
  57 + @Test
  58 + public void testPushMqttTelemetryWithTs() throws Exception {
  59 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  60 + TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 10000);
  61 + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), true);
  62 + }
  63 +
  64 + @Test
  65 + public void testPushMqttTelemetryGateway() throws Exception {
  66 + TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder();
  67 + List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
  68 + String deviceName1 = "Device A";
  69 + String deviceName2 = "Device B";
  70 + TransportApiProtos.TelemetryMsg deviceATelemetryMsgProto = getDeviceTelemetryMsgProto(deviceName1, expectedKeys, 10000, 20000);
  71 + TransportApiProtos.TelemetryMsg deviceBTelemetryMsgProto = getDeviceTelemetryMsgProto(deviceName2, expectedKeys, 10000, 20000);
  72 + gatewayTelemetryMsgProtoBuilder.addAllMsg(Arrays.asList(deviceATelemetryMsgProto, deviceBTelemetryMsgProto));
  73 + TransportApiProtos.GatewayTelemetryMsg gatewayTelemetryMsg = gatewayTelemetryMsgProtoBuilder.build();
  74 + processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, gatewayTelemetryMsg.toByteArray(), deviceName1, deviceName2);
  75 + }
  76 +
  77 + @Test
  78 + public void testGatewayConnect() throws Exception {
  79 + String deviceName = "Device A";
  80 + TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
  81 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  82 + publishMqttMsg(client, connectMsgProto.toByteArray(), MqttTopics.GATEWAY_CONNECT_TOPIC);
  83 +
  84 + Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
  85 + 20,
  86 + 100);
  87 +
  88 + assertNotNull(device);
  89 + }
  90 +
  91 + private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
  92 + TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
  93 + builder.setDeviceName(deviceName);
  94 + builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
  95 + return builder.build();
  96 + }
  97 +
  98 + private TransportApiProtos.TelemetryMsg getDeviceTelemetryMsgProto(String deviceName, List<String> expectedKeys, long firstTs, long secondTs) {
  99 + TransportApiProtos.TelemetryMsg.Builder deviceTelemetryMsgBuilder = TransportApiProtos.TelemetryMsg.newBuilder();
  100 + TransportProtos.TsKvListProto tsKvListProto1 = getTsKvListProto(expectedKeys, firstTs);
  101 + TransportProtos.TsKvListProto tsKvListProto2 = getTsKvListProto(expectedKeys, secondTs);
  102 + TransportProtos.PostTelemetryMsg.Builder msg = TransportProtos.PostTelemetryMsg.newBuilder();
  103 + msg.addAllTsKvList(Arrays.asList(tsKvListProto1, tsKvListProto2));
  104 + deviceTelemetryMsgBuilder.setDeviceName(deviceName);
  105 + deviceTelemetryMsgBuilder.setMsg(msg);
  106 + return deviceTelemetryMsgBuilder.build();
  107 + }
  108 +
  109 + private TransportProtos.TsKvListProto getTsKvListProto(List<String> expectedKeys, long ts) {
  110 + List<TransportProtos.KeyValueProto> kvProtos = getKvProtos(expectedKeys);
  111 + TransportProtos.TsKvListProto.Builder builder = TransportProtos.TsKvListProto.newBuilder();
  112 + builder.addAllKv(kvProtos);
  113 + builder.setTs(ts);
  114 + return builder.build();
  115 + }
  116 +}
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java renamed from application/src/test/java/org/thingsboard/server/mqtt/telemetry/nosql/MqttTelemetryNoSqlIntegrationTest.java
@@ -13,14 +13,14 @@ @@ -13,14 +13,14 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.mqtt.telemetry.nosql; 16 +package org.thingsboard.server.mqtt.telemetry.timeseries.nosql;
17 17
18 import org.thingsboard.server.dao.service.DaoNoSqlTest; 18 import org.thingsboard.server.dao.service.DaoNoSqlTest;
19 -import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest; 19 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest;
20 20
21 /** 21 /**
22 * Created by Valerii Sosliuk on 8/22/2017. 22 * Created by Valerii Sosliuk on 8/22/2017.
23 */ 23 */
24 @DaoNoSqlTest 24 @DaoNoSqlTest
25 -public class MqttTelemetryNoSqlIntegrationTest extends AbstractMqttTelemetryIntegrationTest { 25 +public class MqttTimeseriesNoSqlIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
26 } 26 }
  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.mqtt.telemetry.timeseries.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest;
  20 +
  21 +@DaoNoSqlTest
  22 +public class MqttTimeseriesNoSqlJsonIntegrationTest extends AbstractMqttTimeseriesJsonIntegrationTest {
  23 +}
  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.mqtt.telemetry.timeseries.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest;
  20 +
  21 +@DaoNoSqlTest
  22 +public class MqttTimeseriesNoSqlProtoIntegrationTest extends AbstractMqttTimeseriesProtoIntegrationTest {
  23 +}
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java renamed from application/src/test/java/org/thingsboard/server/mqtt/telemetry/sql/MqttTelemetrySqlIntegrationTest.java
@@ -13,15 +13,14 @@ @@ -13,15 +13,14 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.mqtt.telemetry.sql; 16 +package org.thingsboard.server.mqtt.telemetry.timeseries.sql;
17 17
18 -import org.thingsboard.server.dao.service.DaoNoSqlTest;  
19 import org.thingsboard.server.dao.service.DaoSqlTest; 18 import org.thingsboard.server.dao.service.DaoSqlTest;
20 -import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest; 19 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest;
21 20
22 /** 21 /**
23 * Created by Valerii Sosliuk on 8/22/2017. 22 * Created by Valerii Sosliuk on 8/22/2017.
24 */ 23 */
25 @DaoSqlTest 24 @DaoSqlTest
26 -public class MqttTelemetrySqlIntegrationTest extends AbstractMqttTelemetryIntegrationTest { 25 +public class MqttTimeseriesSqlIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
27 } 26 }
  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.mqtt.telemetry.timeseries.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest;
  20 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest;
  21 +
  22 +/**
  23 + * Created by Valerii Sosliuk on 8/22/2017.
  24 + */
  25 +@DaoSqlTest
  26 +public class MqttTimeseriesSqlJsonIntegrationTest extends AbstractMqttTimeseriesJsonIntegrationTest {
  27 +}
  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.mqtt.telemetry.timeseries.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest;
  20 +import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest;
  21 +
  22 +/**
  23 + * Created by Valerii Sosliuk on 8/22/2017.
  24 + */
  25 +@DaoSqlTest
  26 +public class MqttTimeseriesSqlProtoIntegrationTest extends AbstractMqttTimeseriesProtoIntegrationTest {
  27 +}
@@ -32,7 +32,7 @@ public class RuleEngineSqlTestSuite { @@ -32,7 +32,7 @@ public class RuleEngineSqlTestSuite {
32 32
33 @ClassRule 33 @ClassRule
34 public static CustomSqlUnit sqlUnit = new CustomSqlUnit( 34 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
35 - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), 35 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
36 "sql/hsql/drop-all-tables.sql", 36 "sql/hsql/drop-all-tables.sql",
37 "sql-test.properties"); 37 "sql-test.properties");
38 38
@@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
16 package org.thingsboard.server.rules.flow.sql; 16 package org.thingsboard.server.rules.flow.sql;
17 17
18 import org.thingsboard.server.dao.service.DaoSqlTest; 18 import org.thingsboard.server.dao.service.DaoSqlTest;
19 -import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest;  
20 import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; 19 import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest;
21 20
22 /** 21 /**
@@ -33,7 +33,7 @@ public class SystemSqlTestSuite { @@ -33,7 +33,7 @@ public class SystemSqlTestSuite {
33 33
34 @ClassRule 34 @ClassRule
35 public static CustomSqlUnit sqlUnit = new CustomSqlUnit( 35 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
36 - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), 36 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
37 "sql/hsql/drop-all-tables.sql", 37 "sql/hsql/drop-all-tables.sql",
38 "sql-test.properties"); 38 "sql-test.properties");
39 39
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.dao.rule; 16 package org.thingsboard.server.dao.rule;
17 17
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.exception.ThingsboardException;
19 import org.thingsboard.server.common.data.id.RuleChainId; 20 import org.thingsboard.server.common.data.id.RuleChainId;
20 import org.thingsboard.server.common.data.id.RuleNodeId; 21 import org.thingsboard.server.common.data.id.RuleNodeId;
21 import org.thingsboard.server.common.data.id.TenantId; 22 import org.thingsboard.server.common.data.id.TenantId;
@@ -23,6 +24,8 @@ import org.thingsboard.server.common.data.page.PageData; @@ -23,6 +24,8 @@ import org.thingsboard.server.common.data.page.PageData;
23 import org.thingsboard.server.common.data.page.PageLink; 24 import org.thingsboard.server.common.data.page.PageLink;
24 import org.thingsboard.server.common.data.relation.EntityRelation; 25 import org.thingsboard.server.common.data.relation.EntityRelation;
25 import org.thingsboard.server.common.data.rule.RuleChain; 26 import org.thingsboard.server.common.data.rule.RuleChain;
  27 +import org.thingsboard.server.common.data.rule.RuleChainData;
  28 +import org.thingsboard.server.common.data.rule.RuleChainImportResult;
26 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 29 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
27 import org.thingsboard.server.common.data.rule.RuleNode; 30 import org.thingsboard.server.common.data.rule.RuleNode;
28 31
@@ -63,4 +66,8 @@ public interface RuleChainService { @@ -63,4 +66,8 @@ public interface RuleChainService {
63 66
64 void deleteRuleChainsByTenantId(TenantId tenantId); 67 void deleteRuleChainsByTenantId(TenantId tenantId);
65 68
  69 + RuleChainData exportTenantRuleChains(TenantId tenantId, PageLink pageLink) throws ThingsboardException;
  70 +
  71 + List<RuleChainImportResult> importTenantRuleChains(TenantId tenantId, RuleChainData ruleChainData, boolean overwrite);
  72 +
66 } 73 }
  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.dao.rule;
  17 +
  18 +import org.thingsboard.server.common.data.id.EntityId;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.data.page.PageData;
  22 +import org.thingsboard.server.common.data.page.PageLink;
  23 +import org.thingsboard.server.common.data.rule.RuleNodeState;
  24 +
  25 +public interface RuleNodeStateService {
  26 +
  27 + PageData<RuleNodeState> findByRuleNodeId(TenantId tenantId, RuleNodeId ruleNodeId, PageLink pageLink);
  28 +
  29 + RuleNodeState findByRuleNodeIdAndEntityId(TenantId tenantId, RuleNodeId ruleNodeId, EntityId entityId);
  30 +
  31 + RuleNodeState save(TenantId tenantId, RuleNodeState ruleNodeState);
  32 +
  33 +}
@@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
19 * @author Andrew Shvayka 19 * @author Andrew Shvayka
20 */ 20 */
21 public enum EntityType { 21 public enum EntityType {
22 - TENANT, TENANT_PROFILE, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, DEVICE_PROFILE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE 22 + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE
23 } 23 }
  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.data;
  17 +
  18 +public enum TransportPayloadType {
  19 + JSON,
  20 + PROTOBUF
  21 +}
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.device.profile; 16 package org.thingsboard.server.common.data.device.profile;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
18 import lombok.Data; 19 import lombok.Data;
19 import org.thingsboard.server.common.data.query.KeyFilter; 20 import org.thingsboard.server.common.data.query.KeyFilter;
20 21
@@ -22,10 +23,10 @@ import java.util.List; @@ -22,10 +23,10 @@ import java.util.List;
22 import java.util.concurrent.TimeUnit; 23 import java.util.concurrent.TimeUnit;
23 24
24 @Data 25 @Data
  26 +@JsonIgnoreProperties(ignoreUnknown = true)
25 public class AlarmCondition { 27 public class AlarmCondition {
26 28
27 private List<KeyFilter> condition; 29 private List<KeyFilter> condition;
28 - private TimeUnit durationUnit;  
29 - private long durationValue; 30 + private AlarmConditionSpec spec;
30 31
31 } 32 }
  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.data.device.profile;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  20 +import com.fasterxml.jackson.annotation.JsonSubTypes;
  21 +import com.fasterxml.jackson.annotation.JsonTypeInfo;
  22 +
  23 +@JsonIgnoreProperties(ignoreUnknown = true)
  24 +@JsonTypeInfo(
  25 + use = JsonTypeInfo.Id.NAME,
  26 + include = JsonTypeInfo.As.PROPERTY,
  27 + property = "type")
  28 +@JsonSubTypes({
  29 + @JsonSubTypes.Type(value = SimpleAlarmConditionSpec.class, name = "SIMPLE"),
  30 + @JsonSubTypes.Type(value = DurationAlarmConditionSpec.class, name = "DURATION"),
  31 + @JsonSubTypes.Type(value = RepeatingAlarmConditionSpec.class, name = "REPEATING")})
  32 +public interface AlarmConditionSpec {
  33 +
  34 + @JsonIgnore
  35 + AlarmConditionSpecType getType();
  36 +
  37 +}
  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.data.device.profile;
  17 +
  18 +public enum AlarmConditionSpecType {
  19 +
  20 + SIMPLE,
  21 + DURATION,
  22 + REPEATING
  23 +
  24 +}
@@ -21,6 +21,7 @@ import lombok.Data; @@ -21,6 +21,7 @@ import lombok.Data;
21 public class AlarmRule { 21 public class AlarmRule {
22 22
23 private AlarmCondition condition; 23 private AlarmCondition condition;
  24 + private AlarmSchedule schedule;
24 // Advanced 25 // Advanced
25 private String alarmDetails; 26 private String alarmDetails;
26 27