Browse Source

Merge branch 'develop/1.5' of https://github.com/thingsboard/thingsboard into develop/1.5

pull/773/merge
vparomskiy 8 years ago
parent
commit
81b8f0f40e
  1. 2
      application/src/main/data/upgrade/1.5.0/schema_update.cql
  2. 5
      application/src/main/data/upgrade/1.5.0/schema_update.sql
  3. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 35
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  6. 6
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  7. 123
      application/src/test/java/org/thingsboard/server/system/BaseDeviceOfflineTest.java
  8. 35
      application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
  9. 23
      application/src/test/java/org/thingsboard/server/system/nosql/DeviceOfflineNoSqlTest.java
  10. 23
      application/src/test/java/org/thingsboard/server/system/sql/DeviceOfflineSqlTest.java
  11. 22
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  12. 40
      common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceStatusQuery.java
  13. 2
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java
  14. 74
      dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java
  15. 49
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  16. 34
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineService.java
  17. 62
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineServiceImpl.java
  18. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  19. 28
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java
  20. 13
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java
  21. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  22. 71
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  23. 2
      dao/src/main/resources/cassandra/schema.cql
  24. 4
      dao/src/main/resources/sql/schema.sql
  25. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
  26. 4
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  27. 10
      rule-engine/rule-engine-components/pom.xml
  28. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java
  29. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java
  30. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java
  31. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java
  32. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
  33. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java
  34. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java
  35. 68
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/RuleVelocityUtils.java
  36. 48
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java
  37. 7
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java
  38. 71
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java
  39. 16
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java
  40. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
  41. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java
  42. 9
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
  43. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java
  44. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
  45. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  46. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
  47. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  48. 6
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  49. 9
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java
  50. 8
      transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  51. 6
      transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  52. 28
      transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java
  53. 20
      transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  54. 25
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  55. 7
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
  56. 6
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
  57. 13
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
  58. 9
      ui/src/app/common/types.constant.js
  59. 2
      ui/src/app/locale/locale.constant.js
  60. 23
      ui/src/app/rulechain/rulechain.controller.js
  61. 5
      ui/src/app/rulechain/rulechain.scss
  62. 8
      ui/src/app/rulechain/rulechain.tpl.html
  63. 4
      ui/src/app/rulechain/rulenode.tpl.html
  64. 23
      ui/src/scss/main.scss

2
application/src/main/data/upgrade/1.5.0/schema_update.cql

@ -94,5 +94,3 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
PRIMARY KEY (id)
);
ALTER TABLE thingsboard.device ADD last_connect bigint;
ALTER TABLE thingsboard.device ADD last_update bigint;

5
application/src/main/data/upgrade/1.5.0/schema_update.sql

@ -35,7 +35,4 @@ CREATE TABLE IF NOT EXISTS rule_node (
name varchar(255),
debug_mode boolean,
search_text varchar(255)
);
ALTER TABLE device ADD COLUMN IF NOT EXISTS last_connect BIGINT;
ALTER TABLE device ADD COLUMN IF NOT EXISTS last_update BIGINT;
);

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -45,7 +45,6 @@ import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -135,9 +134,6 @@ public abstract class BaseController {
@Autowired
protected AuditLogService auditLogService;
@Autowired
protected DeviceOfflineService offlineService;
@Autowired
protected DeviceStateService deviceStateService;

35
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -25,9 +25,6 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -37,6 +34,8 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
@ -70,7 +69,7 @@ public class DeviceController extends BaseController {
device.setTenantId(getCurrentUser().getTenantId());
if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) {
if (device.getId() == null || device.getId().isNullUid() ||
device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
throw new ThingsboardException("You don't have permission to perform this operation!",
ThingsboardErrorCode.PERMISSION_DENIED);
} else {
@ -374,32 +373,4 @@ public class DeviceController extends BaseController {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/device/offline", method = RequestMethod.GET)
@ResponseBody
public List<Device> getOfflineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,
@RequestParam("threshold") long threshold) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
ListenableFuture<List<Device>> offlineDevices = offlineService.findOfflineDevices(tenantId.getId(), contactType, threshold);
return checkNotNull(offlineDevices.get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/device/online", method = RequestMethod.GET)
@ResponseBody
public List<Device> getOnlineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,
@RequestParam("threshold") long threshold) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
ListenableFuture<List<Device>> offlineDevices = offlineService.findOnlineDevices(tenantId.getId(), contactType, threshold);
return checkNotNull(offlineDevices.get());
} catch (Exception e) {
throw handleException(e);
}
}
}

3
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -118,6 +118,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
case FILTER:
case TRANSFORMATION:
case ACTION:
case EXTERNAL:
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope());
@ -194,6 +195,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
nodeDefinition.setUiResources(nodeAnnotation.uiResources());
nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
nodeDefinition.setIcon(nodeAnnotation.icon());
nodeDefinition.setIconUrl(nodeAnnotation.iconUrl());
return nodeDefinition;
}

6
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -262,7 +262,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
state.setInactivityTimeout(inactivityTimeout);
boolean oldActive = state.isActive();
state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
if (!oldActive && state.isActive()) {
if (!oldActive && state.isActive() || oldActive && !state.isActive()) {
saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
}
}
@ -333,10 +333,6 @@ public class DefaultDeviceStateService implements DeviceStateService {
});
}
private long getLastPersistTime(List<AttributeKvEntry> attributes) {
return attributes.stream().map(AttributeKvEntry::getLastUpdateTs).max(Long::compare).orElse(0L);
}
private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) {
for (AttributeKvEntry attribute : attributes) {
if (attribute.getKey().equals(attributeName)) {

123
application/src/test/java/org/thingsboard/server/system/BaseDeviceOfflineTest.java

@ -1,123 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.system;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
public class BaseDeviceOfflineTest extends AbstractControllerTest {
private Device deviceA;
private Device deviceB;
private DeviceCredentials credA;
private DeviceCredentials credB;
@Before
public void before() throws Exception {
loginTenantAdmin();
deviceA = createDevice("DevA", "VMS");
credA = getCredentials(deviceA.getUuidId());
deviceB = createDevice("DevB", "SOLAR");
credB = getCredentials(deviceB.getUuidId());
}
@Test
public void offlineDevicesCanBeFoundByLastConnectField() throws Exception {
makeDeviceContact(credA);
Thread.sleep(1000);
makeDeviceContact(credB);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/offline?contactType=CONNECT&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
@Test
public void offlineDevicesCanBeFoundByLastUpdateField() throws Exception {
makeDeviceUpdate(credA);
Thread.sleep(1000);
makeDeviceUpdate(credB);
makeDeviceContact(credA);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/offline?contactType=UPLOAD&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
@Test
public void onlineDevicesCanBeFoundByLastConnectField() throws Exception {
makeDeviceContact(credB);
Thread.sleep(1000);
makeDeviceContact(credA);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/online?contactType=CONNECT&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
@Test
public void onlineDevicesCanBeFoundByLastUpdateField() throws Exception {
makeDeviceUpdate(credB);
Thread.sleep(1000);
makeDeviceUpdate(credA);
makeDeviceContact(credB);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/online?contactType=UPLOAD&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
private Device createDevice(String name, String type) throws Exception {
Device device = new Device();
device.setName(name);
device.setType(type);
long currentTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10);
device.setLastConnectTs(currentTime);
device.setLastUpdateTs(currentTime);
return doPost("/api/device", device, Device.class);
}
private DeviceCredentials getCredentials(UUID deviceId) throws Exception {
return doGet("/api/device/" + deviceId.toString() + "/credentials", DeviceCredentials.class);
}
private void makeDeviceUpdate(DeviceCredentials credentials) throws Exception {
doPost("/api/v1/" + credentials.getCredentialsId() + "/attributes", ImmutableMap.of("keyA", "valueA"), new String[]{});
}
private void makeDeviceContact(DeviceCredentials credentials) throws Exception {
doGet("/api/v1/" + credentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC");
}
}

35
application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.system;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.servlet.ResultActions;
@ -29,9 +28,6 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -52,9 +48,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
device = new Device();
device.setName("My device");
device.setType("default");
long currentTime = System.currentTimeMillis();
device.setLastConnectTs(currentTime);
device.setLastUpdateTs(currentTime);
device = doPost("/api/device", device, Device.class);
deviceCredentials =
@ -74,34 +67,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk());
}
@Test
public void deviceLastContactAndUpdateFieldsAreUpdated() throws Exception {
Device actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);
Long initConnectTs = actualDevice.getLastConnectTs();
Long initUpdateTs = actualDevice.getLastUpdateTs();
assertNotNull(initConnectTs);
assertNotNull(initUpdateTs);
Thread.sleep(50);
doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes", ImmutableMap.of("keyA", "valueA"), new String[]{});
actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);
Long postConnectTs = actualDevice.getLastConnectTs();
Long postUpdateTs = actualDevice.getLastUpdateTs();
System.out.println(postConnectTs + " - " + postUpdateTs + " -> " + (postConnectTs - initConnectTs) + " : " + (postUpdateTs - initUpdateTs));
assertTrue(postConnectTs > initConnectTs);
assertEquals(postConnectTs, postUpdateTs);
Thread.sleep(50);
doGet("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC");
Thread.sleep(50);
actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);
Long getConnectTs = actualDevice.getLastConnectTs();
Long getUpdateTs = actualDevice.getLastUpdateTs();
assertTrue(getConnectTs > postConnectTs);
assertEquals(getUpdateTs, postUpdateTs);
}
protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception {
MockHttpServletRequestBuilder getRequest;
getRequest = get(urlTemplate, urlVariables);

23
application/src/test/java/org/thingsboard/server/system/nosql/DeviceOfflineNoSqlTest.java

@ -1,23 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.system.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.system.BaseDeviceOfflineTest;
@DaoNoSqlTest
public class DeviceOfflineNoSqlTest extends BaseDeviceOfflineTest {
}

23
application/src/test/java/org/thingsboard/server/system/sql/DeviceOfflineSqlTest.java

@ -1,23 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.system.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.system.BaseDeviceOfflineTest;
@DaoSqlTest
public class DeviceOfflineSqlTest extends BaseDeviceOfflineTest {
}

22
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -31,8 +31,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
private CustomerId customerId;
private String name;
private String type;
private Long lastConnectTs;
private Long lastUpdateTs;
public Device() {
super();
@ -83,22 +81,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.type = type;
}
public Long getLastConnectTs() {
return lastConnectTs;
}
public void setLastConnectTs(Long lastConnectTs) {
this.lastConnectTs = lastConnectTs;
}
public Long getLastUpdateTs() {
return lastUpdateTs;
}
public void setLastUpdateTs(Long lastUpdateTs) {
this.lastUpdateTs = lastUpdateTs;
}
@Override
public String getSearchText() {
return getName();
@ -119,10 +101,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
builder.append(getAdditionalInfo());
builder.append(", createdTime=");
builder.append(createdTime);
builder.append(", lastUpdateTs=");
builder.append(lastUpdateTs);
builder.append(", lastConnectTs=");
builder.append(lastConnectTs);
builder.append(", id=");
builder.append(id);
builder.append("]");

40
common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceStatusQuery.java

@ -1,40 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.device;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
@Data
@AllArgsConstructor
@ToString
public class DeviceStatusQuery {
private Status status;
private ContactType contactType;
private long threshold;
public enum Status {
ONLINE, OFFLINE
}
public enum ContactType {
CONNECT, UPLOAD
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java

@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
*/
public enum ComponentType {
ENRICHMENT, FILTER, TRANSFORMATION, ACTION, OLD_ACTION, PLUGIN
ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL, OLD_ACTION, PLUGIN
}

74
dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java

@ -15,9 +15,9 @@
*/
package org.thingsboard.server.dao.device;
import com.datastax.driver.core.*;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.mapping.Result;
import com.google.common.base.Function;
@ -28,11 +28,9 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.EntitySubtypeEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.nosql.DeviceEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.util.NoSqlDao;
@ -159,7 +157,7 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
if (result != null) {
List<EntitySubtype> entitySubtypes = new ArrayList<>();
result.all().forEach((entitySubtypeEntity) ->
entitySubtypes.add(entitySubtypeEntity.toEntitySubtype())
entitySubtypes.add(entitySubtypeEntity.toEntitySubtype())
);
return entitySubtypes;
} else {
@ -169,68 +167,4 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
});
}
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery) {
log.debug("Try to find [{}] devices by tenantId [{}]", statusQuery.getStatus(), tenantId);
Select select = select().from(DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME).allowFiltering();
Select.Where query = select.where();
query.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId));
Clause clause = statusClause(statusQuery);
query.and(clause);
return findListByStatementAsync(query);
}
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery) {
log.debug("Try to find [{}] devices by tenantId [{}] and type [{}]", statusQuery.getStatus(), tenantId, type);
Select select = select().from(DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME).allowFiltering();
Select.Where query = select.where()
.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId))
.and(eq(DEVICE_TYPE_PROPERTY, type));
query.and(statusClause(statusQuery));
return findListByStatementAsync(query);
}
@Override
public void saveDeviceStatus(Device device) {
PreparedStatement statement = prepare("insert into " +
"device (id, tenant_id, customer_id, type, last_connect, last_update) values (?, ?, ?, ?, ?, ?)");
BoundStatement boundStatement = statement.bind(device.getUuidId(), device.getTenantId().getId(), device.getCustomerId().getId(),
device.getType(), device.getLastConnectTs(), device.getLastUpdateTs());
ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement);
Futures.withFallback(resultSetFuture, t -> {
log.error("Can't update device status for [{}]", device, t);
throw new IllegalArgumentException("Can't update device status for {" + device + "}", t);
});
}
private String getStatusProperty(DeviceStatusQuery statusQuery) {
switch (statusQuery.getContactType()) {
case UPLOAD:
return DEVICE_LAST_UPDATE_PROPERTY;
case CONNECT:
return DEVICE_LAST_CONNECT_PROPERTY;
}
return null;
}
private Clause statusClause(DeviceStatusQuery statusQuery) {
long minTime = System.currentTimeMillis() - statusQuery.getThreshold();
String statusProperty = getStatusProperty(statusQuery);
if (statusProperty != null) {
switch (statusQuery.getStatus()) {
case ONLINE:
return gt(statusProperty, minTime);
case OFFLINE:
return lt(statusProperty, minTime);
}
}
log.error("Could not build status query from [{}]", statusQuery);
throw new IllegalStateException("Could not build status query for device []");
}
}

49
dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java

@ -18,7 +18,6 @@ package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
@ -28,6 +27,7 @@ import java.util.UUID;
/**
* The Interface DeviceDao.
*
*/
public interface DeviceDao extends Dao<Device> {
@ -52,7 +52,7 @@ public interface DeviceDao extends Dao<Device> {
* Find devices by tenantId, type and page link.
*
* @param tenantId the tenantId
* @param type the type
* @param type the type
* @param pageLink the page link
* @return the list of device objects
*/
@ -61,7 +61,7 @@ public interface DeviceDao extends Dao<Device> {
/**
* Find devices by tenantId and devices Ids.
*
* @param tenantId the tenantId
* @param tenantId the tenantId
* @param deviceIds the device Ids
* @return the list of device objects
*/
@ -70,9 +70,9 @@ public interface DeviceDao extends Dao<Device> {
/**
* Find devices by tenantId, customerId and page link.
*
* @param tenantId the tenantId
* @param tenantId the tenantId
* @param customerId the customerId
* @param pageLink the page link
* @param pageLink the page link
* @return the list of device objects
*/
List<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
@ -80,10 +80,10 @@ public interface DeviceDao extends Dao<Device> {
/**
* Find devices by tenantId, customerId, type and page link.
*
* @param tenantId the tenantId
* @param tenantId the tenantId
* @param customerId the customerId
* @param type the type
* @param pageLink the page link
* @param type the type
* @param pageLink the page link
* @return the list of device objects
*/
List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink);
@ -92,9 +92,9 @@ public interface DeviceDao extends Dao<Device> {
/**
* Find devices by tenantId, customerId and devices Ids.
*
* @param tenantId the tenantId
* @param tenantId the tenantId
* @param customerId the customerId
* @param deviceIds the device Ids
* @param deviceIds the device Ids
* @return the list of device objects
*/
ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds);
@ -103,7 +103,7 @@ public interface DeviceDao extends Dao<Device> {
* Find devices by tenantId and device name.
*
* @param tenantId the tenantId
* @param name the device name
* @param name the device name
* @return the optional device object
*/
Optional<Device> findDeviceByTenantIdAndName(UUID tenantId, String name);
@ -114,31 +114,4 @@ public interface DeviceDao extends Dao<Device> {
* @return the list of tenant device type objects
*/
ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId);
/**
* Find devices by tenantId, statusQuery and page link.
*
* @param tenantId the tenantId
* @param statusQuery the page link
* @return the list of device objects
*/
ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery);
/**
* Find devices by tenantId, type, statusQuery and page link.
*
* @param tenantId the tenantId
* @param type the type
* @param statusQuery the page link
* @return the list of device objects
*/
ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery);
/**
* Update device last contact and update timestamp async
*
* @param device the device object
*/
void saveDeviceStatus(Device device);
}

34
dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineService.java

@ -1,34 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import java.util.List;
import java.util.UUID;
public interface DeviceOfflineService {
void online(Device device, boolean isUpdate);
void offline(Device device);
ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold);
ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold);
}

62
dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineServiceImpl.java

@ -1,62 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.common.data.device.DeviceStatusQuery.Status.OFFLINE;
import static org.thingsboard.server.common.data.device.DeviceStatusQuery.Status.ONLINE;
@Service
public class DeviceOfflineServiceImpl implements DeviceOfflineService {
@Autowired
private DeviceDao deviceDao;
@Override
public void online(Device device, boolean isUpdate) {
long current = System.currentTimeMillis();
device.setLastConnectTs(current);
if(isUpdate) {
device.setLastUpdateTs(current);
}
deviceDao.saveDeviceStatus(device);
}
@Override
public void offline(Device device) {
online(device, false);
}
@Override
public ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
DeviceStatusQuery statusQuery = new DeviceStatusQuery(OFFLINE, contactType, offlineThreshold);
return deviceDao.findDevicesByTenantIdAndStatus(tenantId, statusQuery);
}
@Override
public ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
DeviceStatusQuery statusQuery = new DeviceStatusQuery(ONLINE, contactType, offlineThreshold);
return deviceDao.findDevicesByTenantIdAndStatus(tenantId, statusQuery);
}
}

2
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -133,8 +133,6 @@ public class ModelConstants {
public static final String DEVICE_NAME_PROPERTY = "name";
public static final String DEVICE_TYPE_PROPERTY = "type";
public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String DEVICE_LAST_CONNECT_PROPERTY = "last_connect";
public static final String DEVICE_LAST_UPDATE_PROPERTY = "last_update";
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";

28
dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java

@ -63,12 +63,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
@Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
private JsonNode additionalInfo;
@Column(name = DEVICE_LAST_CONNECT_PROPERTY)
private Long lastConnectTs;
@Column(name = DEVICE_LAST_UPDATE_PROPERTY)
private Long lastUpdateTs;
public DeviceEntity() {
super();
}
@ -86,8 +80,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
this.name = device.getName();
this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo();
this.lastConnectTs = device.getLastConnectTs();
this.lastUpdateTs = device.getLastUpdateTs();
}
public UUID getId() {
@ -137,23 +129,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
public void setAdditionalInfo(JsonNode additionalInfo) {
this.additionalInfo = additionalInfo;
}
public Long getLastConnectTs() {
return lastConnectTs;
}
public void setLastConnectTs(Long lastConnectTs) {
this.lastConnectTs = lastConnectTs;
}
public Long getLastUpdateTs() {
return lastUpdateTs;
}
public void setLastUpdateTs(Long lastUpdateTs) {
this.lastUpdateTs = lastUpdateTs;
}
@Override
public String getSearchTextSource() {
return getName();
@ -181,8 +157,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
device.setName(name);
device.setType(type);
device.setAdditionalInfo(additionalInfo);
device.setLastConnectTs(lastConnectTs);
device.setLastUpdateTs(lastUpdateTs);
return device;
}

13
dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java

@ -34,9 +34,6 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_LAST_CONNECT_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_LAST_UPDATE_PROPERTY;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@ -63,12 +60,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
@Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@Column(name = DEVICE_LAST_CONNECT_PROPERTY)
private Long lastConnectTs;
@Column(name = DEVICE_LAST_UPDATE_PROPERTY)
private Long lastUpdateTs;
public DeviceEntity() {
super();
}
@ -86,8 +77,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
this.name = device.getName();
this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo();
this.lastConnectTs = device.getLastConnectTs();
this.lastUpdateTs = device.getLastUpdateTs();
}
@Override
@ -113,8 +102,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
device.setName(name);
device.setType(type);
device.setAdditionalInfo(additionalInfo);
device.setLastConnectTs(lastConnectTs);
device.setLastUpdateTs(lastUpdateTs);
return device;
}
}

24
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -79,28 +79,4 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> {
List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds);
List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs > :time")
List<DeviceEntity> findConnectOnlineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs < :time")
List<DeviceEntity> findConnectOfflineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs > :time")
List<DeviceEntity> findUpdateOnlineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs < :time")
List<DeviceEntity> findUpdateOfflineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs > :time AND d.type = :type")
List<DeviceEntity> findConnectOnlineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs < :time AND d.type = :type")
List<DeviceEntity> findConnectOfflineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs > :time AND d.type = :type")
List<DeviceEntity> findUpdateOnlineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs < :time AND d.type = :type")
List<DeviceEntity> findUpdateOfflineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
}

71
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -15,9 +15,7 @@
*/
package org.thingsboard.server.dao.sql.device;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository;
@ -26,7 +24,6 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
@ -46,7 +43,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
*/
@Component
@SqlDao
@Slf4j
public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao {
@Autowired
@ -128,73 +124,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId))));
}
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery) {
String strTenantId = fromTimeUUID(tenantId);
long minTime = System.currentTimeMillis() - statusQuery.getThreshold();
switch (statusQuery.getStatus()) {
case OFFLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOfflineByTenantId(strTenantId, minTime)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOfflineByTenantId(strTenantId, minTime)));
}
break;
}
case ONLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOnlineByTenantId(strTenantId, minTime)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOnlineByTenantId(strTenantId, minTime)));
}
break;
}
}
log.error("Could not build status query from [{}]", statusQuery);
throw new IllegalStateException("Could not build status query for device []");
}
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery) {
String strTenantId = fromTimeUUID(tenantId);
long minTime = System.currentTimeMillis() - statusQuery.getThreshold();
switch (statusQuery.getStatus()) {
case OFFLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOfflineByTenantIdAndType(strTenantId, minTime, type)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOfflineByTenantIdAndType(strTenantId, minTime, type)));
}
break;
}
case ONLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOnlineByTenantIdAndType(strTenantId, minTime, type)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOnlineByTenantIdAndType(strTenantId, minTime, type)));
}
break;
}
}
log.error("Could not build status query from [{}]", statusQuery);
throw new IllegalStateException("Could not build status query for device []");
}
@Override
public void saveDeviceStatus(Device device) {
ListenableFuture<Device> future = service.submit(() -> save(device));
Futures.withFallback(future, t -> {
log.error("Can't update device status for [{}]", device, t);
throw new IllegalArgumentException("Can't update device status for {" + device + "}", t);
});
}
private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {

2
dao/src/main/resources/cassandra/schema.cql

@ -159,8 +159,6 @@ CREATE TABLE IF NOT EXISTS thingsboard.device (
type text,
search_text text,
additional_info text,
last_connect bigint,
last_update bigint,
PRIMARY KEY (id, tenant_id, customer_id, type)
);

4
dao/src/main/resources/sql/schema.sql

@ -118,9 +118,7 @@ CREATE TABLE IF NOT EXISTS device (
type varchar(255),
name varchar(255),
search_text varchar(255),
tenant_id varchar(31),
last_connect bigint,
last_update bigint
tenant_id varchar(31)
);
CREATE TABLE IF NOT EXISTS device_credentials (

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java

@ -31,5 +31,7 @@ public class NodeDefinition {
JsonNode defaultConfiguration;
String[] uiResources;
String configDirective;
String icon;
String iconUrl;
}

4
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java

@ -49,6 +49,10 @@ public @interface RuleNode {
String configDirective() default "";
String icon() default "";
String iconUrl() default "";
boolean customRelations() default false;
}

10
rule-engine/rule-engine-components/pom.xml

@ -72,16 +72,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java

@ -47,7 +47,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
"Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeAlarmConfig")
configDirective = "tbActionNodeAlarmConfig",
icon = "notifications_active"
)
public class TbAlarmNode implements TbNode {

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java

@ -34,7 +34,9 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
"Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeLogConfig")
configDirective = "tbActionNodeLogConfig",
icon = "menu"
)
public class TbLogNode implements TbNode {

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java

@ -38,13 +38,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "aws sns",
configClazz = TbSnsNodeConfiguration.class,
nodeDescription = "Publish messages to AWS SNS",
nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeSnsConfig"
configDirective = "tbActionNodeSnsConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
)
public class TbSnsNode implements TbNode {

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java

@ -41,13 +41,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "aws sqs",
configClazz = TbSqsNodeConfiguration.class,
nodeDescription = "Publish messages to AWS SQS",
nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeSqsConfig"
configDirective = "tbActionNodeSqsConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
)
public class TbSqsNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java

@ -41,7 +41,8 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
nodeDetails = "Generates messages with configurable period. ",
inEnabled = false,
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeGeneratorConfig"
configDirective = "tbActionNodeGeneratorConfig",
icon = "repeat"
)
public class TbMsgGeneratorNode implements TbNode {

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java

@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import java.util.Arrays;
import java.util.Collections;
@ -33,7 +34,10 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration<TbMsg
@Override
public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
configuration.setMessageTypes(Arrays.asList("POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST"));
configuration.setMessageTypes(Arrays.asList(
SessionMsgType.POST_ATTRIBUTES_REQUEST.name(),
SessionMsgType.POST_TELEMETRY_REQUEST.name(),
SessionMsgType.TO_SERVER_RPC_REQUEST.name()));
return configuration;
}
}

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java

@ -28,13 +28,14 @@ import java.util.concurrent.ExecutionException;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "kafka",
configClazz = TbKafkaNodeConfiguration.class,
nodeDescription = "Publish messages to Kafka server",
nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeKafkaConfig"
configDirective = "tbActionNodeKafkaConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUzOCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDQxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTIwMS44MTYgMjMwLjIxNmMtMTYuMTg2IDAtMzAuNjk3IDcuMTcxLTQwLjYzNCAxOC40NjFsLTI1LjQ2My0xOC4wMjZjMi43MDMtNy40NDIgNC4yNTUtMTUuNDMzIDQuMjU1LTIzLjc5NyAwLTguMjE5LTEuNDk4LTE2LjA3Ni00LjExMi0yMy40MDhsMjUuNDA2LTE3LjgzNWM5LjkzNiAxMS4yMzMgMjQuNDA5IDE4LjM2NSA0MC41NDggMTguMzY1IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI5Ljg3OS0yNC4zMDktNTQuMTg0LTU0LjE4NC01NC4xODQtMjkuODc1IDAtNTQuMTg0IDI0LjMwNS01NC4xODQgNTQuMTg0IDAgNS4zNDguODA4IDEwLjUwNSAyLjI1OCAxNS4zODlsLTI1LjQyMyAxNy44NDRjLTEwLjYyLTEzLjE3NS0yNS45MTEtMjIuMzc0LTQzLjMzMy0yNS4xODJ2LTMwLjY0YzI0LjU0NC01LjE1NSA0My4wMzctMjYuOTYyIDQzLjAzNy01My4wMTlDMTI0LjE3MSAyNC4zMDUgOTkuODYyIDAgNjkuOTg3IDAgNDAuMTEyIDAgMTUuODAzIDI0LjMwNSAxNS44MDMgNTQuMTg0YzAgMjUuNzA4IDE4LjAxNCA0Ny4yNDYgNDIuMDY3IDUyLjc2OXYzMS4wMzhDMjUuMDQ0IDE0My43NTMgMCAxNzIuNDAxIDAgMjA2Ljg1NGMwIDM0LjYyMSAyNS4yOTIgNjMuMzc0IDU4LjM1NSA2OC45NHYzMi43NzRjLTI0LjI5OSA1LjM0MS00Mi41NTIgMjcuMDExLTQyLjU1MiA1Mi44OTQgMCAyOS44NzkgMjQuMzA5IDU0LjE4NCA1NC4xODQgNTQuMTg0IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI1Ljg4My0xOC4yNTMtNDcuNTUzLTQyLjU1Mi01Mi44OTR2LTMyLjc3NWE2OS45NjUgNjkuOTY1IDAgMCAwIDQyLjYtMjQuNzc2bDI1LjYzMyAxOC4xNDNjLTEuNDIzIDQuODQtMi4yMiA5Ljk0Ni0yLjIyIDE1LjI0IDAgMjkuODc5IDI0LjMwOSA1NC4xODQgNTQuMTg0IDU0LjE4NCAyOS44NzUgMCA1NC4xODQtMjQuMzA1IDU0LjE4NC01NC4xODQgMC0yOS44NzktMjQuMzA5LTU0LjE4NC01NC4xODQtNTQuMTg0em0wLTEyNi42OTVjMTQuNDg3IDAgMjYuMjcgMTEuNzg4IDI2LjI3IDI2LjI3MXMtMTEuNzgzIDI2LjI3LTI2LjI3IDI2LjI3LTI2LjI3LTExLjc4Ny0yNi4yNy0yNi4yN2MwLTE0LjQ4MyAxMS43ODMtMjYuMjcxIDI2LjI3LTI2LjI3MXptLTE1OC4xLTQ5LjMzN2MwLTE0LjQ4MyAxMS43ODQtMjYuMjcgMjYuMjcxLTI2LjI3czI2LjI3IDExLjc4NyAyNi4yNyAyNi4yN2MwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3em01Mi41NDEgMzA3LjI3OGMwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3YzAtMTQuNDgzIDExLjc4NC0yNi4yNyAyNi4yNzEtMjYuMjdzMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3em0tMjYuMjcyLTExNy45N2MtMjAuMjA1IDAtMzYuNjQyLTE2LjQzNC0zNi42NDItMzYuNjM4IDAtMjAuMjA1IDE2LjQzNy0zNi42NDIgMzYuNjQyLTM2LjY0MiAyMC4yMDQgMCAzNi42NDEgMTYuNDM3IDM2LjY0MSAzNi42NDIgMCAyMC4yMDQtMTYuNDM3IDM2LjYzOC0zNi42NDEgMzYuNjM4em0xMzEuODMxIDY3LjE3OWMtMTQuNDg3IDAtMjYuMjctMTEuNzg4LTI2LjI3LTI2LjI3MXMxMS43ODMtMjYuMjcgMjYuMjctMjYuMjcgMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3YzAgMTQuNDgzLTExLjc4MyAyNi4yNzEtMjYuMjcgMjYuMjcxeiIvPjwvc3ZnPg=="
)
public class TbKafkaNode implements TbNode {

68
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/RuleVelocityUtils.java

@ -1,68 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.mail;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.thingsboard.server.common.msg.TbMsg;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;
import static org.thingsboard.server.common.msg.TbMsgDataType.JSON;
public class RuleVelocityUtils {
public static VelocityContext createContext(TbMsg msg) throws IOException {
VelocityContext context = new VelocityContext();
context.put("originator", msg.getOriginator());
context.put("type", msg.getType());
context.put("metadata", msg.getMetaData().values());
if (msg.getDataType() == JSON) {
Map map = new ObjectMapper().readValue(msg.getData(), Map.class);
context.put("msg", map);
} else {
context.put("msg", msg.getData());
}
return context;
}
public static String merge(Template template, VelocityContext context) {
StringWriter writer = new StringWriter();
template.merge(context, writer);
return writer.toString();
}
public static Template create(String source, String templateName) throws ParseException {
RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
StringReader reader = new StringReader(source);
SimpleNode node = runtimeServices.parse(reader, templateName);
Template template = new Template();
template.setRuntimeServices(runtimeServices);
template.setData(node);
template.initDocument();
return template;
}
}

48
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java

@ -18,17 +18,14 @@ package org.thingsboard.rule.engine.mail;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.parser.ParseException;
import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.io.IOException;
import java.util.Optional;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE;
@ -42,34 +39,18 @@ import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE;
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbTransformationNodeToEmailConfig")
configDirective = "tbTransformationNodeToEmailConfig",
icon = "email"
)
public class TbMsgToEmailNode implements TbNode {
private static final ObjectMapper MAPPER = new ObjectMapper();
private TbMsgToEmailNodeConfiguration config;
private Optional<Template> fromTemplate;
private Optional<Template> toTemplate;
private Optional<Template> ccTemplate;
private Optional<Template> bccTemplate;
private Optional<Template> subjectTemplate;
private Optional<Template> bodyTemplate;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class);
try {
fromTemplate = toTemplate(config.getFromTemplate(), "From Template");
toTemplate = toTemplate(config.getToTemplate(), "To Template");
ccTemplate = toTemplate(config.getCcTemplate(), "Cc Template");
bccTemplate = toTemplate(config.getBccTemplate(), "Bcc Template");
subjectTemplate = toTemplate(config.getSubjectTemplate(), "Subject Template");
bodyTemplate = toTemplate(config.getBodyTemplate(), "Body Template");
} catch (ParseException e) {
log.error("Failed to create templates based on provided configuration!", e);
throw new TbNodeException(e);
}
}
@Override
@ -91,21 +72,20 @@ public class TbMsgToEmailNode implements TbNode {
private EmailPojo convert(TbMsg msg) throws IOException {
EmailPojo.EmailPojoBuilder builder = EmailPojo.builder();
VelocityContext context = RuleVelocityUtils.createContext(msg);
fromTemplate.ifPresent(t -> builder.from(RuleVelocityUtils.merge(t, context)));
toTemplate.ifPresent(t -> builder.to(RuleVelocityUtils.merge(t, context)));
ccTemplate.ifPresent(t -> builder.cc(RuleVelocityUtils.merge(t, context)));
bccTemplate.ifPresent(t -> builder.bcc(RuleVelocityUtils.merge(t, context)));
subjectTemplate.ifPresent(t -> builder.subject(RuleVelocityUtils.merge(t, context)));
bodyTemplate.ifPresent(t -> builder.body(RuleVelocityUtils.merge(t, context)));
builder.from(fromTemplate(this.config.getFromTemplate(), msg.getMetaData()));
builder.to(fromTemplate(this.config.getToTemplate(), msg.getMetaData()));
builder.cc(fromTemplate(this.config.getCcTemplate(), msg.getMetaData()));
builder.bcc(fromTemplate(this.config.getBccTemplate(), msg.getMetaData()));
builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg.getMetaData()));
builder.body(fromTemplate(this.config.getBodyTemplate(), msg.getMetaData()));
return builder.build();
}
private Optional<Template> toTemplate(String source, String name) throws ParseException {
if (!StringUtils.isEmpty(source)) {
return Optional.of(RuleVelocityUtils.create(source, name));
private String fromTemplate(String template, TbMsgMetaData metaData) {
if (!StringUtils.isEmpty(template)) {
return TbNodeUtils.processPattern(template, metaData);
} else {
return Optional.empty();
return null;
}
}

7
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java

@ -32,10 +32,9 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration {
public TbMsgToEmailNodeConfiguration defaultConfiguration() {
TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration();
configuration.fromTemplate = "info@testmail.org";
configuration.toTemplate = "$metadata.userEmail";
configuration.subjectTemplate = "Device $deviceType temperature high";
configuration.bodyTemplate = "Device $metadata.deviceName has high temperature $msg.temp";
configuration.toTemplate = "${userEmail}";
configuration.subjectTemplate = "Device ${deviceType} temperature high";
configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}";
return configuration;
}
}

71
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java

@ -15,38 +15,57 @@
*/
package org.thingsboard.rule.engine.mail;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.util.Properties;
import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "send email",
configClazz = TbSendEmailNodeConfiguration.class,
nodeDescription = "Log incoming messages using JS script for transformation Message into String",
nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " +
"Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>")
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeSendEmailConfig",
icon = "send"
)
public class TbSendEmailNode implements TbNode {
private static final String MAIL_PROP = "mail.";
static final String SEND_EMAIL_TYPE = "SEND_EMAIL";
private static final ObjectMapper MAPPER = new ObjectMapper();
private TbSendEmailNodeConfiguration config;
private JavaMailSenderImpl mailSender;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbSendEmailNodeConfiguration.class);
try {
this.config = TbNodeUtils.convert(configuration, TbSendEmailNodeConfiguration.class);
if (!this.config.isUseSystemSmtpSettings()) {
mailSender = createMailSender();
}
} catch (Exception e) {
throw new TbNodeException(e);
}
}
@Override
@ -55,8 +74,7 @@ public class TbSendEmailNode implements TbNode {
validateType(msg.getType());
EmailPojo email = getEmail(msg);
withCallback(ctx.getMailExecutor().executeAsync(() -> {
ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(),
email.getBcc(), email.getSubject(), email.getBody());
sendEmail(ctx, email);
return null;
}),
ok -> ctx.tellNext(msg, SUCCESS),
@ -66,6 +84,27 @@ public class TbSendEmailNode implements TbNode {
}
}
private void sendEmail(TbContext ctx, EmailPojo email) throws Exception {
if (this.config.isUseSystemSmtpSettings()) {
ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(),
email.getBcc(), email.getSubject(), email.getBody());
} else {
MimeMessage mailMsg = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8");
helper.setFrom(email.getFrom());
helper.setTo(email.getTo().split("\\s*,\\s*"));
if (!StringUtils.isBlank(email.getCc())) {
helper.setCc(email.getCc().split("\\s*,\\s*"));
}
if (!StringUtils.isBlank(email.getBcc())) {
helper.setBcc(email.getBcc().split("\\s*,\\s*"));
}
helper.setSubject(email.getSubject());
helper.setText(email.getBody());
mailSender.send(helper.getMimeMessage());
}
}
private EmailPojo getEmail(TbMsg msg) throws IOException {
EmailPojo email = MAPPER.readValue(msg.getData(), EmailPojo.class);
if (StringUtils.isBlank(email.getTo())) {
@ -84,4 +123,26 @@ public class TbSendEmailNode implements TbNode {
@Override
public void destroy() {
}
private JavaMailSenderImpl createMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(this.config.getSmtpHost());
mailSender.setPort(this.config.getSmtpPort());
mailSender.setUsername(this.config.getUsername());
mailSender.setPassword(this.config.getPassword());
mailSender.setJavaMailProperties(createJavaMailProperties());
return mailSender;
}
private Properties createJavaMailProperties() {
Properties javaMailProperties = new Properties();
String protocol = this.config.getSmtpProtocol();
javaMailProperties.put("mail.transport.protocol", protocol);
javaMailProperties.put(MAIL_PROP + protocol + ".host", this.config.getSmtpHost());
javaMailProperties.put(MAIL_PROP + protocol + ".port", this.config.getSmtpPort()+"");
javaMailProperties.put(MAIL_PROP + protocol + ".timeout", this.config.getTimeout()+"");
javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(this.config.getUsername())));
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", Boolean.valueOf(this.config.isEnableTls()).toString());
return javaMailProperties;
}
}

16
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java

@ -21,12 +21,24 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data
public class TbSendEmailNodeConfiguration implements NodeConfiguration {
private String tmp;
private boolean useSystemSmtpSettings;
private String smtpHost;
private int smtpPort;
private String username;
private String password;
private String smtpProtocol;
private int timeout;
private boolean enableTls;
@Override
public TbSendEmailNodeConfiguration defaultConfiguration() {
TbSendEmailNodeConfiguration configuration = new TbSendEmailNodeConfiguration();
configuration.tmp = "";
configuration.setUseSystemSmtpSettings(true);
configuration.setSmtpHost("localhost");
configuration.setSmtpProtocol("smtp");
configuration.setSmtpPort(25);
configuration.setTimeout(10000);
configuration.setEnableTls(false);
return configuration;
}
}

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java

@ -43,13 +43,14 @@ import java.util.concurrent.TimeoutException;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "mqtt",
configClazz = TbMqttNodeConfiguration.class,
nodeDescription = "Publish messages to MQTT broker",
nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeMqttConfig"
configDirective = "tbActionNodeMqttConfig",
icon = "call_split"
)
public class TbMqttNode implements TbNode {

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java

@ -33,13 +33,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "rabbitmq",
configClazz = TbRabbitMqNodeConfiguration.class,
nodeDescription = "Publish messages to RabbitMQ",
nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRabbitMqConfig"
configDirective = "tbActionNodeRabbitMqConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZlcnNpb249IjEuMSIgeT0iMHB4IiB4PSIwcHgiIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIHN0cm9rZS13aWR0aD0iLjg0OTU2IiBkPSJtODYwLjQ3IDQxNi4zMmgtMjYyLjAxYy0xMi45MTMgMC0yMy42MTgtMTAuNzA0LTIzLjYxOC0yMy42MTh2LTI3Mi43MWMwLTIwLjMwNS0xNi4yMjctMzYuMjc2LTM2LjI3Ni0zNi4yNzZoLTkzLjc5MmMtMjAuMzA1IDAtMzYuMjc2IDE2LjIyNy0zNi4yNzYgMzYuMjc2djI3MC44NGMtMC4yNTQ4NyAxNC4xMDMtMTEuNDY5IDI1LjU3Mi0yNS43NDIgMjUuNTcybC04NS42MzYgMC42Nzk2NWMtMTQuMTAzIDAtMjUuNTcyLTExLjQ2OS0yNS41NzItMjUuNTcybDAuNjc5NjUtMjcxLjUyYzAtMjAuMzA1LTE2LjIyNy0zNi4yNzYtMzYuMjc2LTM2LjI3NmgtOTMuNTM3Yy0yMC4zMDUgMC0zNi4yNzYgMTYuMjI3LTM2LjI3NiAzNi4yNzZ2NzYzLjg0YzAgMTguMDk2IDE0Ljc4MiAzMi40NTMgMzIuNDUzIDMyLjQ1M2g3MjIuODFjMTguMDk2IDAgMzIuNDUzLTE0Ljc4MiAzMi40NTMtMzIuNDUzdi00MzUuMzFjLTEuMTg5NC0xOC4xODEtMTUuMjkyLTMyLjE5OC0zMy4zODgtMzIuMTk4em0tMTIyLjY4IDI4Ny4wN2MwIDIzLjYxOC0xOC44NiA0Mi40NzgtNDIuNDc4IDQyLjQ3OGgtNzMuOTk3Yy0yMy42MTggMC00Mi40NzgtMTguODYtNDIuNDc4LTQyLjQ3OHYtNzQuMjUyYzAtMjMuNjE4IDE4Ljg2LTQyLjQ3OCA0Mi40NzgtNDIuNDc4aDczLjk5N2MyMy42MTggMCA0Mi40NzggMTguODYgNDIuNDc4IDQyLjQ3OHoiLz48L3N2Zz4="
)
public class TbRabbitMqNode implements TbNode {

9
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java

@ -26,7 +26,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.client.*;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.HttpClientErrorException;
import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType;
@ -34,19 +35,19 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.net.ssl.SSLException;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "rest api call",
configClazz = TbRestApiCallNodeConfiguration.class,
nodeDescription = "Invoke REST API calls to external REST server",
nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRestApiCallConfig"
configDirective = "tbActionNodeRestApiCallConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB2ZXJzaW9uPSIxLjEiIHk9IjBweCIgeD0iMHB4Ij48ZyB0cmFuc2Zvcm09Im1hdHJpeCguOTQ5NzUgMCAwIC45NDk3NSAxNy4xMiAyNi40OTIpIj48cGF0aCBkPSJtMTY5LjExIDEwOC41NGMtOS45MDY2IDAuMDczNC0xOS4wMTQgNi41NzI0LTIyLjAxNCAxNi40NjlsLTY5Ljk5MyAyMzEuMDhjLTMuNjkwNCAxMi4xODEgMy4yODkyIDI1LjIyIDE1LjQ2OSAyOC45MSAyLjIyNTkgMC42NzQ4MSA0LjQ5NjkgMSA2LjcyODUgMSA5Ljk3MjEgMCAxOS4xNjUtNi41MTUzIDIyLjE4Mi0xNi40NjdhNi41MjI0IDYuNTIyNCAwIDAgMCAwLjAwMiAtMC4wMDJsNjkuOTktMjMxLjA3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMCAtMC4wMDJjMy42ODU1LTEyLjE4MS0zLjI4Ny0yNS4yMjUtMTUuNDcxLTI4LjkxMi0yLjI4MjUtMC42OTE0NS00LjYxMTYtMS4wMTY5LTYuODk4NC0xem04NC45ODggMGMtOS45MDQ4IDAuMDczNC0xOS4wMTggNi41Njc1LTIyLjAxOCAxNi40NjlsLTY5Ljk4NiAyMzEuMDhjLTMuNjg5OCAxMi4xNzkgMy4yODUzIDI1LjIxNyAxNS40NjUgMjguOTA4IDIuMjI5NyAwLjY3NjQ3IDQuNTAwOCAxLjAwMiA2LjczMjQgMS4wMDIgOS45NzIxIDAgMTkuMTY1LTYuNTE1MyAyMi4xODItMTYuNDY3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMC4wMDIgLTAuMDAybDY5Ljk4OC0yMzEuMDdjMy42OTA4LTEyLjE4MS0zLjI4NTItMjUuMjIzLTE1LjQ2Ny0yOC45MTItMi4yODE0LTAuNjkyMzEtNC42MTA4LTEuMDE4OS02Ljg5ODQtMS4wMDJ6bS0yMTcuMjkgNDIuMjNjLTEyLjcyOS0wLjAwMDg3LTIzLjE4OCAxMC40NTYtMjMuMTg4IDIzLjE4NiAwLjAwMSAxMi43MjggMTAuNDU5IDIzLjE4NiAyMy4xODggMjMuMTg2IDEyLjcyNy0wLjAwMSAyMy4xODMtMTAuNDU5IDIzLjE4NC0yMy4xODYgMC4wMDA4NzYtMTIuNzI4LTEwLjQ1Ni0yMy4xODUtMjMuMTg0LTIzLjE4NnptMCAxNDYuNjRjLTEyLjcyNy0wLjAwMDg3LTIzLjE4NiAxMC40NTUtMjMuMTg4IDIzLjE4NC0wLjAwMDg3MyAxMi43MjkgMTAuNDU4IDIzLjE4OCAyMy4xODggMjMuMTg4IDEyLjcyOC0wLjAwMSAyMy4xODQtMTAuNDYgMjMuMTg0LTIzLjE4OC0wLjAwMS0xMi43MjYtMTAuNDU3LTIzLjE4My0yMy4xODQtMjMuMTg0em0yNzAuNzkgNDIuMjExYy0xMi43MjcgMC0yMy4xODQgMTAuNDU3LTIzLjE4NCAyMy4xODRzMTAuNDU1IDIzLjE4OCAyMy4xODQgMjMuMTg4aDE1NC45OGMxMi43MjkgMCAyMy4xODYtMTAuNDYgMjMuMTg2LTIzLjE4OCAwLjAwMS0xMi43MjgtMTAuNDU4LTIzLjE4NC0yMy4xODYtMjMuMTg0eiIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMzc2IDAgMCAxLjAzNzYgLTcuNTY3NiAtMTQuOTI1KSIgc3Ryb2tlLXdpZHRoPSIxLjI2OTMiLz48L2c+PC9zdmc+"
)
public class TbRestApiCallNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java

@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg;
nodeDescription = "Sends reply to the RPC call from device",
nodeDetails = "Expects messages with any message type. Will forward message body to the device.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRpcReplyConfig"
configDirective = "tbActionNodeRpcReplyConfig",
icon = "call_merge"
)
public class TbSendRPCReplyNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java

@ -43,7 +43,8 @@ import java.util.concurrent.TimeUnit;
nodeDescription = "Sends one-way RPC call to device",
nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRpcRequestConfig"
configDirective = "tbActionNodeRpcRequestConfig",
icon = "call_made"
)
public class TbSendRPCRequestNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java

@ -48,7 +48,8 @@ import java.util.Set;
nodeDescription = "Saves attributes data",
nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeAttributesConfig"
configDirective = "tbActionNodeAttributesConfig",
icon = "file_upload"
)
public class TbMsgAttributesNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java

@ -45,7 +45,8 @@ import java.util.Map;
nodeDescription = "Saves timeseries data",
nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeTimeseriesConfig"
configDirective = "tbActionNodeTimeseriesConfig",
icon = "file_upload"
)
public class TbMsgTimeseriesNode implements TbNode {

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java

@ -40,7 +40,9 @@ import java.util.HashSet;
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbTransformationNodeChangeOriginatorConfig")
configDirective = "tbTransformationNodeChangeOriginatorConfig",
icon = "find_replace"
)
public class TbChangeOriginatorNode extends TbAbstractTransformNode {
protected static final String CUSTOMER_SOURCE = "CUSTOMER";

6
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

9
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java

@ -59,6 +59,9 @@ public class TbMsgToEmailNodeTest {
initWithScript();
metaData.putValue("username", "oreo");
metaData.putValue("userEmail", "user@email.io");
metaData.putValue("name", "temp");
metaData.putValue("passed", "5");
metaData.putValue("count", "100");
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
emailNode.onMsg(ctx, msg);
@ -91,9 +94,9 @@ public class TbMsgToEmailNodeTest {
try {
TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration();
config.setFromTemplate("test@mail.org");
config.setToTemplate("$metadata.userEmail");
config.setSubjectTemplate("Hi $metadata.username there");
config.setBodyTemplate("$msg.name is to high. Current $msg.passed and $msg.complex.count");
config.setToTemplate("${userEmail}");
config.setSubjectTemplate("Hi ${username} there");
config.setBodyTemplate("${name} is to high. Current ${passed} and ${count}");
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));

8
transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -35,7 +35,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy;
import org.thingsboard.server.transport.coap.session.CoapSessionCtx;
@ -54,17 +53,15 @@ public class CoapTransportResource extends CoapResource {
private final SessionMsgProcessor processor;
private final DeviceAuthService authService;
private final QuotaService quotaService;
private final DeviceOfflineService offlineService;
private final Field observerField;
private final long timeout;
public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name,
long timeout, QuotaService quotaService, DeviceOfflineService offlineService) {
long timeout, QuotaService quotaService) {
super(name);
this.processor = processor;
this.authService = authService;
this.quotaService = quotaService;
this.offlineService = offlineService;
this.adaptor = adaptor;
this.timeout = timeout;
// This is important to turn off existing observable logic in
@ -171,7 +168,6 @@ public class CoapTransportResource extends CoapResource {
case TO_SERVER_RPC_REQUEST:
ctx.setSessionType(SessionType.SYNC);
msg = adaptor.convertToActorMsg(ctx, type, request);
offlineService.online(ctx.getDevice(), true);
break;
case SUBSCRIBE_ATTRIBUTES_REQUEST:
case SUBSCRIBE_RPC_COMMANDS_REQUEST:
@ -179,13 +175,11 @@ public class CoapTransportResource extends CoapResource {
advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx));
ctx.setSessionType(SessionType.ASYNC);
msg = adaptor.convertToActorMsg(ctx, type, request);
offlineService.online(ctx.getDevice(), false);
break;
case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
ctx.setSessionType(SessionType.ASYNC);
msg = adaptor.convertToActorMsg(ctx, type, request);
offlineService.online(ctx.getDevice(), false);
break;
default:
log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type);

6
transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

@ -27,7 +27,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
import javax.annotation.PostConstruct;
@ -58,9 +57,6 @@ public class CoapTransportService {
@Autowired(required = false)
private QuotaService quotaService;
@Autowired(required = false)
private DeviceOfflineService offlineService;
@Value("${coap.bind_address}")
private String host;
@ -90,7 +86,7 @@ public class CoapTransportService {
private void createResources() {
CoapResource api = new CoapResource(API);
api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService, offlineService));
api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService));
server.add(api);
}

28
transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.transport.coap;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
@ -32,7 +31,6 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -53,7 +51,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthResult;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import java.util.ArrayList;
import java.util.List;
@ -140,31 +137,6 @@ public class CoapServerTest {
public static QuotaService quotaService() {
return key -> false;
}
@Bean
public static DeviceOfflineService offlineService() {
return new DeviceOfflineService() {
@Override
public void online(Device device, boolean isUpdate) {
}
@Override
public void offline(Device device) {
}
@Override
public ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
return null;
}
@Override
public ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
return null;
}
};
}
}
@Autowired

20
transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -26,7 +26,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
import org.thingsboard.server.common.msg.core.*;
import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg;
@ -37,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.transport.http.session.HttpSessionCtx;
import javax.servlet.http.HttpServletRequest;
@ -65,9 +63,6 @@ public class DeviceApiController {
@Autowired(required = false)
private QuotaService quotaService;
@Autowired(required = false)
private DeviceOfflineService offlineService;
@RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json")
public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys,
@ -87,7 +82,7 @@ public class DeviceApiController {
Set<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null;
request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet);
}
process(ctx, request, false);
process(ctx, request);
} else {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
@ -105,7 +100,7 @@ public class DeviceApiController {
HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try {
process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json)), true);
process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json)));
} catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
}
@ -125,7 +120,7 @@ public class DeviceApiController {
HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try {
process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json)), true);
process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json)));
} catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
}
@ -155,7 +150,7 @@ public class DeviceApiController {
if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try {
JsonObject response = new JsonParser().parse(json).getAsJsonObject();
process(ctx, new ToDeviceRpcResponseMsg(requestId, response.toString()), true);
process(ctx, new ToDeviceRpcResponseMsg(requestId, response.toString()));
} catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
}
@ -178,7 +173,7 @@ public class DeviceApiController {
JsonObject request = new JsonParser().parse(json).getAsJsonObject();
process(ctx, new ToServerRpcRequestMsg(0,
request.get("method").getAsString(),
request.get("params").toString()), true);
request.get("params").toString()));
} catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
}
@ -204,7 +199,7 @@ public class DeviceApiController {
HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout);
if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try {
process(ctx, msg, false);
process(ctx, msg);
} catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
}
@ -222,10 +217,9 @@ public class DeviceApiController {
return new HttpSessionCtx(processor, authService, responseWriter, timeout != 0 ? timeout : defaultTimeout);
}
private void process(HttpSessionCtx ctx, FromDeviceMsg request, boolean isUpdate) {
private void process(HttpSessionCtx ctx, FromDeviceMsg request) {
AdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ctx, request);
processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg));
offlineService.online(ctx.getDevice(), isUpdate);
}
private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) {

25
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -37,7 +37,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.EncryptionUtil;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@ -73,14 +72,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private final DeviceAuthService authService;
private final RelationService relationService;
private final QuotaService quotaService;
private final DeviceOfflineService offlineService;
private final SslHandler sslHandler;
private volatile boolean connected;
private volatile InetSocketAddress address;
private volatile GatewaySessionCtx gatewaySessionCtx;
public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService, DeviceOfflineService offlineService) {
MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService) {
this.processor = processor;
this.deviceService = deviceService;
this.relationService = relationService;
@ -90,7 +88,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
this.sessionId = deviceSessionCtx.getSessionId().toUidStr();
this.sslHandler = sslHandler;
this.quotaService = quotaService;
this.offlineService = offlineService;
}
@Override
@ -132,13 +129,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
case PINGREQ:
if (checkConnected(ctx)) {
ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
offlineService.online(deviceSessionCtx.getDevice(), false);
}
break;
case DISCONNECT:
if (checkConnected(ctx)) {
processDisconnect(ctx);
offlineService.offline(deviceSessionCtx.getDevice());
}
break;
default:
@ -190,28 +185,23 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
try {
if (topicName.equals(DEVICE_TELEMETRY_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_TELEMETRY_REQUEST, mqttMsg);
offlineService.online(deviceSessionCtx.getDevice(), true);
} else if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_ATTRIBUTES_REQUEST, mqttMsg);
offlineService.online(deviceSessionCtx.getDevice(), true);
} else if (topicName.startsWith(DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, GET_ATTRIBUTES_REQUEST, mqttMsg);
if (msgId >= 0) {
ctx.writeAndFlush(createMqttPubAckMsg(msgId));
}
offlineService.online(deviceSessionCtx.getDevice(), false);
} else if (topicName.startsWith(DEVICE_RPC_RESPONSE_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_DEVICE_RPC_RESPONSE, mqttMsg);
if (msgId >= 0) {
ctx.writeAndFlush(createMqttPubAckMsg(msgId));
}
offlineService.online(deviceSessionCtx.getDevice(), true);
} else if (topicName.startsWith(DEVICE_RPC_REQUESTS_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_SERVER_RPC_REQUEST, mqttMsg);
if (msgId >= 0) {
ctx.writeAndFlush(createMqttPubAckMsg(msgId));
}
offlineService.online(deviceSessionCtx.getDevice(), true);
}
} catch (AdaptorException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
@ -260,7 +250,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
}
ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList));
offlineService.online(deviceSessionCtx.getDevice(), false);
}
private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
@ -284,7 +273,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
}
ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId()));
offlineService.online(deviceSessionCtx.getDevice(), false);
}
private MqttMessage createUnSubAckMessage(int msgId) {
@ -316,7 +304,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
connected = true;
checkGatewaySession();
offlineService.online(deviceSessionCtx.getDevice(), false);
}
}
@ -328,7 +315,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
connected = true;
checkGatewaySession();
offlineService.online(deviceSessionCtx.getDevice(), false);
} else {
ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED));
ctx.close();
@ -379,9 +365,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error("[{}] Unexpected Exception", sessionId, cause);
ctx.close();
if(deviceSessionCtx.getDevice() != null) {
offlineService.offline(deviceSessionCtx.getDevice());
}
}
private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) {
@ -420,8 +403,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
if (infoNode != null) {
JsonNode gatewayNode = infoNode.get("gateway");
if (gatewayNode != null && gatewayNode.asBoolean()) {
gatewaySessionCtx = new GatewaySessionCtx(processor, deviceService, authService,
relationService, deviceSessionCtx, offlineService);
gatewaySessionCtx = new GatewaySessionCtx(processor, deviceService, authService, relationService, deviceSessionCtx);
}
}
}
@ -429,8 +411,5 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
processor.process(SessionCloseMsg.onError(deviceSessionCtx.getSessionId()));
if(deviceSessionCtx.getDevice() != null) {
offlineService.offline(deviceSessionCtx.getDevice());
}
}
}

7
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java

@ -24,7 +24,6 @@ import io.netty.handler.ssl.SslHandler;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@ -43,11 +42,10 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
private final MqttTransportAdaptor adaptor;
private final MqttSslHandlerProvider sslHandlerProvider;
private final QuotaService quotaService;
private final DeviceOfflineService offlineService;
public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider,
QuotaService quotaService, DeviceOfflineService offlineService) {
QuotaService quotaService) {
this.processor = processor;
this.deviceService = deviceService;
this.authService = authService;
@ -55,7 +53,6 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
this.adaptor = adaptor;
this.sslHandlerProvider = sslHandlerProvider;
this.quotaService = quotaService;
this.offlineService = offlineService;
}
@Override
@ -70,7 +67,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
pipeline.addLast("encoder", MqttEncoder.INSTANCE);
MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService,
adaptor, sslHandler, quotaService, offlineService);
adaptor, sslHandler, quotaService);
pipeline.addLast(handler);
ch.closeFuture().addListener(handler);

6
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java

@ -30,7 +30,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@ -70,9 +69,6 @@ public class MqttTransportService {
@Autowired(required = false)
private QuotaService quotaService;
@Autowired(required = false)
private DeviceOfflineService offlineService;
@Value("${mqtt.bind_address}")
private String host;
@Value("${mqtt.bind_port}")
@ -110,7 +106,7 @@ public class MqttTransportService {
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService,
adaptor, sslHandlerProvider, quotaService, offlineService));
adaptor, sslHandlerProvider, quotaService));
serverChannel = b.bind(host, port).sync().channel();
log.info("Mqtt transport started!");

13
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java

@ -36,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
@ -62,17 +61,14 @@ public class GatewaySessionCtx {
private final DeviceService deviceService;
private final DeviceAuthService authService;
private final RelationService relationService;
private final DeviceOfflineService offlineService;
private final Map<String, GatewayDeviceSessionCtx> devices;
private ChannelHandlerContext channel;
public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService,
RelationService relationService, DeviceSessionCtx gatewaySessionCtx, DeviceOfflineService offlineService) {
public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, DeviceSessionCtx gatewaySessionCtx) {
this.processor = processor;
this.deviceService = deviceService;
this.authService = authService;
this.relationService = relationService;
this.offlineService = offlineService;
this.gateway = gatewaySessionCtx.getDevice();
this.gatewaySessionId = gatewaySessionCtx.getSessionId();
this.devices = new HashMap<>();
@ -102,7 +98,6 @@ public class GatewaySessionCtx {
log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName);
processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg())));
processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new RpcSubscribeMsg())));
offlineService.online(device, false);
}
}
@ -112,7 +107,6 @@ public class GatewaySessionCtx {
if (deviceSessionCtx != null) {
processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId()));
deviceSessionCtx.setClosed(true);
offlineService.offline(deviceSessionCtx.getDevice());
log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName);
} else {
log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName);
@ -123,7 +117,6 @@ public class GatewaySessionCtx {
public void onGatewayDisconnect() {
devices.forEach((k, v) -> {
processor.process(SessionCloseMsg.onDisconnect(v.getSessionId()));
offlineService.offline(v.getDevice());
});
}
@ -145,7 +138,6 @@ public class GatewaySessionCtx {
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
offlineService.online(deviceSessionCtx.getDevice(), true);
}
} else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
@ -162,7 +154,6 @@ public class GatewaySessionCtx {
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data))));
offlineService.online(deviceSessionCtx.getDevice(), true);
} else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
}
@ -185,7 +176,6 @@ public class GatewaySessionCtx {
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
offlineService.online(deviceSessionCtx.getDevice(), true);
}
} else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
@ -220,7 +210,6 @@ public class GatewaySessionCtx {
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
ack(msg);
offlineService.online(deviceSessionCtx.getDevice(), false);
} else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
}

9
ui/src/app/common/types.constant.js

@ -485,7 +485,7 @@ export default angular.module('thingsboard.types', [])
clientSide: false
}
},
ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"],
ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"],
ruleChainNodeComponent: {
type: 'RULE_CHAIN',
name: 'rule chain',
@ -536,6 +536,13 @@ export default angular.module('thingsboard.types', [])
nodeClass: "tb-action-type",
icon: "flash_on"
},
EXTERNAL: {
value: "EXTERNAL",
name: "rulenode.type-external",
details: "rulenode.type-external-details",
nodeClass: "tb-external-type",
icon: "cloud_upload"
},
RULE_CHAIN: {
value: "RULE_CHAIN",
name: "rulenode.type-rule-chain",

2
ui/src/app/locale/locale.constant.js

@ -1228,6 +1228,8 @@ export default angular.module('thingsboard.locale', [])
"type-transformation-details": "Change Message payload and Metadata",
"type-action": "Action",
"type-action-details": "Perform special action",
"type-external": "External",
"type-external-details": "Interacts with external system",
"type-rule-chain": "Rule Chain",
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",

23
ui/src/app/rulechain/rulechain.controller.js

@ -246,6 +246,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var contextInfo = {
headerClass: node.nodeClass,
icon: node.icon,
iconUrl: node.iconUrl,
title: node.name,
subtitle: node.component.name
};
@ -805,12 +806,21 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var ruleNodeComponent = ruleNodeComponents[i];
componentType = ruleNodeComponent.type;
var model = vm.ruleNodeTypesModel[componentType].model;
var icon = vm.types.ruleNodeType[componentType].icon;
var iconUrl = null;
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.icon) {
icon = ruleNodeComponent.configurationDescriptor.nodeDefinition.icon;
}
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl) {
iconUrl = ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl;
}
var node = {
id: 'node-lib-' + componentType + '-' + model.nodes.length,
component: ruleNodeComponent,
name: '',
nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
icon: vm.types.ruleNodeType[componentType].icon,
icon: icon,
iconUrl: iconUrl,
x: 30,
y: 10+50*model.nodes.length,
connectors: []
@ -904,6 +914,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var ruleNode = vm.ruleChainMetaData.nodes[i];
var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
if (component) {
var icon = vm.types.ruleNodeType[component.type].icon;
var iconUrl = null;
if (component.configurationDescriptor.nodeDefinition.icon) {
icon = component.configurationDescriptor.nodeDefinition.icon;
}
if (component.configurationDescriptor.nodeDefinition.iconUrl) {
iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl;
}
var node = {
id: 'rule-chain-node-' + vm.nextNodeID++,
ruleNodeId: ruleNode.id,
@ -915,7 +933,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
component: component,
name: ruleNode.name,
nodeClass: vm.types.ruleNodeType[component.type].nodeClass,
icon: vm.types.ruleNodeType[component.type].icon,
icon: icon,
iconUrl: iconUrl,
connectors: []
};
if (component.configurationDescriptor.nodeDefinition.inEnabled) {

5
ui/src/app/rulechain/rulechain.scss

@ -76,7 +76,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
min-width: 180px;
min-width: 150px;
}
.fc-canvas {
background: #f9f9f9;
@ -161,6 +161,9 @@
&.tb-action-type {
background-color: #f1928f;
}
&.tb-external-type {
background-color: #fbc766;
}
&.tb-rule-chain-type {
background-color: #d6c4f1;
}

8
ui/src/app/rulechain/rulechain.tpl.html

@ -75,6 +75,8 @@
<md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel">
<md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
ng-mouseleave="vm.destroyTooltips()">
<md-icon aria-label="node-type-icon"
class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
<div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed>
@ -82,6 +84,8 @@
<md-expansion-panel-header ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
ng-mouseleave="vm.destroyTooltips()"
ng-click="vm.$mdExpansionPanel(typeId).collapse()">
<md-icon aria-label="node-type-icon"
class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
<div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header>
@ -114,8 +118,10 @@
</div>
<md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()">
<div class="tb-context-menu-header {{vm.contextInfo.headerClass}}">
<md-icon aria-label="node-type-icon"
<md-icon ng-if="!vm.contextInfo.iconUrl" aria-label="node-type-icon"
class="material-icons">{{vm.contextInfo.icon}}</md-icon>
<md-icon ng-if="vm.contextInfo.iconUrl" aria-label="node-type-icon"
md-svg-icon="{{vm.contextInfo.iconUrl}}"></md-icon>
<div flex>
<div class="tb-context-menu-title">{{vm.contextInfo.title}}</div>
<div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div>

4
ui/src/app/rulechain/rulenode.tpl.html

@ -24,8 +24,10 @@
ng-mouseleave="callbacks.mouseLeave($event, node)">
<div class="{{flowchartConstants.nodeOverlayClass}}"></div>
<div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }">
<md-icon aria-label="node-type-icon" flex="15"
<md-icon ng-if="!node.iconUrl" aria-label="node-type-icon" flex="15"
class="material-icons">{{node.icon}}</md-icon>
<md-icon ng-if="node.iconUrl" aria-label="node-type-icon" flex="15"
md-svg-icon="{{node.iconUrl}}"></md-icon>
<div layout="column" flex="85" layout-align="center">
<span class="tb-node-type">{{ node.component.name }}</span>
<span class="tb-node-title" ng-if="node.name">{{ node.name }}</span>

23
ui/src/scss/main.scss

@ -267,6 +267,29 @@ div {
}
}
.tb-hint {
font-size: 12px;
line-height: 14px;
transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2);
color: grey;
padding-bottom: 15px;
&.ng-hide, &.ng-enter, &.ng-leave.ng-leave-active {
bottom: 26px;
opacity: 0;
}
&.ng-leave, &.ng-enter.ng-enter-active {
bottom: 7px;
opacity: 1;
}
}
md-input-container {
.tk-hint {
padding-top: 40px;
}
}
.md-caption {
&.tb-required:after {
content: ' *';

Loading…
Cancel
Save