Commit c4b89e29efab81c00b3f8eb6705eb3b3d46479a6

Authored by Igor Kulikov
2 parents 305ec306 77d754ad

Merge branch 'master' of github.com:thingsboard/thingsboard

@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 "configuration": null 8 "configuration": null
9 }, 9 },
10 "metadata": { 10 "metadata": {
11 - "firstNodeIndex": 2, 11 + "firstNodeIndex": 6,
12 "nodes": [ 12 "nodes": [
13 { 13 {
14 "additionalInfo": { 14 "additionalInfo": {
@@ -82,10 +82,29 @@ @@ -82,10 +82,29 @@
82 "configuration": { 82 "configuration": {
83 "timeoutInSeconds": 60 83 "timeoutInSeconds": 60
84 } 84 }
  85 + },
  86 + {
  87 + "additionalInfo": {
  88 + "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
  89 + "layoutX": 204,
  90 + "layoutY": 240
  91 + },
  92 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
  93 + "name": "Device Profile Node",
  94 + "debugMode": false,
  95 + "configuration": {
  96 + "persistAlarmRulesState": false,
  97 + "fetchAlarmRulesStateOnStart": false
  98 + }
85 } 99 }
86 ], 100 ],
87 "connections": [ 101 "connections": [
88 { 102 {
  103 + "fromIndex": 6,
  104 + "toIndex": 2,
  105 + "type": "Success"
  106 + },
  107 + {
89 "fromIndex": 2, 108 "fromIndex": 2,
90 "toIndex": 4, 109 "toIndex": 4,
91 "type": "Other" 110 "type": "Other"
@@ -181,6 +181,7 @@ public class ThingsboardInstallService { @@ -181,6 +181,7 @@ public class ThingsboardInstallService {
181 databaseTsUpgradeService.upgradeDatabase("3.1.1"); 181 databaseTsUpgradeService.upgradeDatabase("3.1.1");
182 } 182 }
183 databaseEntitiesUpgradeService.upgradeDatabase("3.1.1"); 183 databaseEntitiesUpgradeService.upgradeDatabase("3.1.1");
  184 + dataUpdateService.updateData("3.1.1");
184 log.info("Updating system data..."); 185 log.info("Updating system data...");
185 systemDataLoaderService.updateSystemWidgets(); 186 systemDataLoaderService.updateSystemWidgets();
186 break; 187 break;
@@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
15 */ 15 */
16 package org.thingsboard.server.service.install.update; 16 package org.thingsboard.server.service.install.update;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 import com.google.common.util.concurrent.FutureCallback; 20 import com.google.common.util.concurrent.FutureCallback;
19 import com.google.common.util.concurrent.Futures; 21 import com.google.common.util.concurrent.Futures;
20 import com.google.common.util.concurrent.ListenableFuture; 22 import com.google.common.util.concurrent.ListenableFuture;
@@ -23,9 +25,13 @@ import lombok.extern.slf4j.Slf4j; @@ -23,9 +25,13 @@ import lombok.extern.slf4j.Slf4j;
23 import org.springframework.beans.factory.annotation.Autowired; 25 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.context.annotation.Profile; 26 import org.springframework.context.annotation.Profile;
25 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
  28 +import org.springframework.util.StringUtils;
  29 +import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
  30 +import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
26 import org.thingsboard.server.common.data.EntityView; 31 import org.thingsboard.server.common.data.EntityView;
27 import org.thingsboard.server.common.data.SearchTextBased; 32 import org.thingsboard.server.common.data.SearchTextBased;
28 import org.thingsboard.server.common.data.Tenant; 33 import org.thingsboard.server.common.data.Tenant;
  34 +import org.thingsboard.server.common.data.id.EntityId;
29 import org.thingsboard.server.common.data.id.EntityViewId; 35 import org.thingsboard.server.common.data.id.EntityViewId;
30 import org.thingsboard.server.common.data.id.TenantId; 36 import org.thingsboard.server.common.data.id.TenantId;
31 import org.thingsboard.server.common.data.id.UUIDBased; 37 import org.thingsboard.server.common.data.id.UUIDBased;
@@ -35,10 +41,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -35,10 +41,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
35 import org.thingsboard.server.common.data.page.PageData; 41 import org.thingsboard.server.common.data.page.PageData;
36 import org.thingsboard.server.common.data.page.PageLink; 42 import org.thingsboard.server.common.data.page.PageLink;
37 import org.thingsboard.server.common.data.rule.RuleChain; 43 import org.thingsboard.server.common.data.rule.RuleChain;
  44 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  45 +import org.thingsboard.server.common.data.rule.RuleNode;
38 import org.thingsboard.server.dao.entityview.EntityViewService; 46 import org.thingsboard.server.dao.entityview.EntityViewService;
39 import org.thingsboard.server.dao.rule.RuleChainService; 47 import org.thingsboard.server.dao.rule.RuleChainService;
40 import org.thingsboard.server.dao.tenant.TenantService; 48 import org.thingsboard.server.dao.tenant.TenantService;
41 import org.thingsboard.server.dao.timeseries.TimeseriesService; 49 import org.thingsboard.server.dao.timeseries.TimeseriesService;
  50 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
42 import org.thingsboard.server.service.install.InstallScripts; 51 import org.thingsboard.server.service.install.InstallScripts;
43 52
44 import javax.annotation.Nullable; 53 import javax.annotation.Nullable;
@@ -49,6 +58,7 @@ import java.util.concurrent.ExecutionException; @@ -49,6 +58,7 @@ import java.util.concurrent.ExecutionException;
49 import java.util.stream.Collectors; 58 import java.util.stream.Collectors;
50 59
51 import static org.apache.commons.lang.StringUtils.isBlank; 60 import static org.apache.commons.lang.StringUtils.isBlank;
  61 +import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
52 62
53 @Service 63 @Service
54 @Profile("install") 64 @Profile("install")
@@ -81,6 +91,10 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -81,6 +91,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
81 log.info("Updating data from version 3.0.1 to 3.1.0 ..."); 91 log.info("Updating data from version 3.0.1 to 3.1.0 ...");
82 tenantsEntityViewsUpdater.updateEntities(null); 92 tenantsEntityViewsUpdater.updateEntities(null);
83 break; 93 break;
  94 + case "3.1.1":
  95 + log.info("Updating data from version 3.1.1 to 3.2.0 ...");
  96 + tenantsRootRuleChainUpdater.updateEntities(null);
  97 + break;
84 default: 98 default:
85 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); 99 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
86 } 100 }
@@ -107,6 +121,60 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -107,6 +121,60 @@ public class DefaultDataUpdateService implements DataUpdateService {
107 } 121 }
108 }; 122 };
109 123
  124 + private PaginatedUpdater<String, Tenant> tenantsRootRuleChainUpdater =
  125 + new PaginatedUpdater<String, Tenant>() {
  126 +
  127 + @Override
  128 + protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
  129 + return tenantService.findTenants(pageLink);
  130 + }
  131 +
  132 + @Override
  133 + protected void updateEntity(Tenant tenant) {
  134 + try {
  135 + RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId());
  136 + if (ruleChain == null) {
  137 + installScripts.createDefaultRuleChains(tenant.getId());
  138 + } else {
  139 + RuleChainMetaData md = ruleChainService.loadRuleChainMetaData(tenant.getId(), ruleChain.getId());
  140 + int oldIdx = md.getFirstNodeIndex();
  141 + int newIdx = md.getNodes().size();
  142 +
  143 + if (md.getNodes().size() < oldIdx) {
  144 + // Skip invalid rule chains
  145 + return;
  146 + }
  147 +
  148 + RuleNode oldFirstNode = md.getNodes().get(oldIdx);
  149 + if (oldFirstNode.getType().equals(TbDeviceProfileNode.class.getName())) {
  150 + // No need to update the rule node twice.
  151 + return;
  152 + }
  153 +
  154 + RuleNode ruleNode = new RuleNode();
  155 + ruleNode.setRuleChainId(ruleChain.getId());
  156 + ruleNode.setName("Device Profile Node");
  157 + ruleNode.setType(TbDeviceProfileNode.class.getName());
  158 + ruleNode.setDebugMode(false);
  159 + TbDeviceProfileNodeConfiguration ruleNodeConfiguration = new TbDeviceProfileNodeConfiguration().defaultConfiguration();
  160 + ruleNode.setConfiguration(JacksonUtil.valueToTree(ruleNodeConfiguration));
  161 + ObjectNode additionalInfo = JacksonUtil.newObjectNode();
  162 + additionalInfo.put("description", "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.");
  163 + additionalInfo.put("layoutX", 204);
  164 + additionalInfo.put("layoutY", 240);
  165 + ruleNode.setAdditionalInfo(additionalInfo);
  166 +
  167 + md.getNodes().add(ruleNode);
  168 + md.setFirstNodeIndex(newIdx);
  169 + md.addConnectionInfo(newIdx, oldIdx, "Success");
  170 + ruleChainService.saveRuleChainMetaData(tenant.getId(), md);
  171 + }
  172 + } catch (Exception e) {
  173 + log.error("Unable to update Tenant", e);
  174 + }
  175 + }
  176 + };
  177 +
110 private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater = 178 private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater =
111 new PaginatedUpdater<String, Tenant>() { 179 new PaginatedUpdater<String, Tenant>() {
112 180
@@ -121,30 +189,30 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -121,30 +189,30 @@ public class DefaultDataUpdateService implements DataUpdateService {
121 } 189 }
122 }; 190 };
123 191
124 - private void updateTenantEntityViews(TenantId tenantId) {  
125 - PageLink pageLink = new PageLink(100);  
126 - PageData<EntityView> pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);  
127 - boolean hasNext = true;  
128 - while (hasNext) {  
129 - List<ListenableFuture<List<Void>>> updateFutures = new ArrayList<>();  
130 - for (EntityView entityView : pageData.getData()) {  
131 - updateFutures.add(updateEntityViewLatestTelemetry(entityView));  
132 - }  
133 -  
134 - try {  
135 - Futures.allAsList(updateFutures).get();  
136 - } catch (InterruptedException | ExecutionException e) {  
137 - log.error("Failed to copy latest telemetry to entity view", e);  
138 - }  
139 -  
140 - if (pageData.hasNext()) {  
141 - pageLink = pageLink.nextPageLink();  
142 - pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);  
143 - } else {  
144 - hasNext = false;  
145 - }  
146 - }  
147 - } 192 + private void updateTenantEntityViews(TenantId tenantId) {
  193 + PageLink pageLink = new PageLink(100);
  194 + PageData<EntityView> pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
  195 + boolean hasNext = true;
  196 + while (hasNext) {
  197 + List<ListenableFuture<List<Void>>> updateFutures = new ArrayList<>();
  198 + for (EntityView entityView : pageData.getData()) {
  199 + updateFutures.add(updateEntityViewLatestTelemetry(entityView));
  200 + }
  201 +
  202 + try {
  203 + Futures.allAsList(updateFutures).get();
  204 + } catch (InterruptedException | ExecutionException e) {
  205 + log.error("Failed to copy latest telemetry to entity view", e);
  206 + }
  207 +
  208 + if (pageData.hasNext()) {
  209 + pageLink = pageLink.nextPageLink();
  210 + pageData = entityViewService.findEntityViewByTenantId(tenantId, pageLink);
  211 + } else {
  212 + hasNext = false;
  213 + }
  214 + }
  215 + }
148 216
149 private ListenableFuture<List<Void>> updateEntityViewLatestTelemetry(EntityView entityView) { 217 private ListenableFuture<List<Void>> updateEntityViewLatestTelemetry(EntityView entityView) {
150 EntityViewId entityId = entityView.getId(); 218 EntityViewId entityId = entityView.getId();
@@ -160,13 +228,13 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -160,13 +228,13 @@ public class DefaultDataUpdateService implements DataUpdateService {
160 keysFuture = Futures.immediateFuture(keys); 228 keysFuture = Futures.immediateFuture(keys);
161 } 229 }
162 ListenableFuture<List<TsKvEntry>> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> { 230 ListenableFuture<List<TsKvEntry>> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> {
163 - List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());  
164 - if (!queries.isEmpty()) {  
165 - return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries);  
166 - } else {  
167 - return Futures.immediateFuture(null);  
168 - }  
169 - }, MoreExecutors.directExecutor()); 231 + List<ReadTsKvQuery> queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList());
  232 + if (!queries.isEmpty()) {
  233 + return tsService.findAll(TenantId.SYS_TENANT_ID, entityView.getEntityId(), queries);
  234 + } else {
  235 + return Futures.immediateFuture(null);
  236 + }
  237 + }, MoreExecutors.directExecutor());
170 return Futures.transformAsync(latestFuture, latestValues -> { 238 return Futures.transformAsync(latestFuture, latestValues -> {
171 if (latestValues != null && !latestValues.isEmpty()) { 239 if (latestValues != null && !latestValues.isEmpty()) {
172 ListenableFuture<List<Void>> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues); 240 ListenableFuture<List<Void>> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues);
@@ -467,9 +467,20 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -467,9 +467,20 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
467 ctx.addStringParameter("where_relation_type", entityFilter.getRelationType()); 467 ctx.addStringParameter("where_relation_type", entityFilter.getRelationType());
468 whereFilter += " re.relation_type = :where_relation_type AND"; 468 whereFilter += " re.relation_type = :where_relation_type AND";
469 } 469 }
  470 + String toOrFrom = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from");
470 whereFilter += " re." + (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from") + "_type = :where_entity_type"; 471 whereFilter += " re." + (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from") + "_type = :where_entity_type";
471 if (entityFilter.isFetchLastLevelOnly()) { 472 if (entityFilter.isFetchLastLevelOnly()) {
472 - whereFilter += " and re.lvl = " + entityFilter.getMaxLevel(); 473 + String fromOrTo = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "from" : "to");
  474 + StringBuilder notExistsPart = new StringBuilder();
  475 + notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr where ")
  476 + .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id")
  477 + .append(" and ")
  478 + .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type");
  479 + if (!StringUtils.isEmpty(entityFilter.getRelationType())) {
  480 + notExistsPart.append(" and nr.relation_type = :where_relation_type");
  481 + }
  482 + notExistsPart.append(")");
  483 + whereFilter += " and ( re.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")";
473 } 484 }
474 from = String.format(from, lvlFilter, whereFilter); 485 from = String.format(from, lvlFilter, whereFilter);
475 String query = "( " + selectFields + from + ")"; 486 String query = "( " + selectFields + from + ")";
@@ -502,14 +513,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -502,14 +513,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
502 StringBuilder whereFilter = new StringBuilder(); 513 StringBuilder whereFilter = new StringBuilder();
503 514
504 boolean noConditions = true; 515 boolean noConditions = true;
  516 + boolean single = entityFilter.getFilters() != null && entityFilter.getFilters().size() == 1;
505 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) { 517 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) {
506 - boolean single = entityFilter.getFilters().size() == 1;  
507 int entityTypeFilterIdx = 0; 518 int entityTypeFilterIdx = 0;
508 for (EntityTypeFilter etf : entityFilter.getFilters()) { 519 for (EntityTypeFilter etf : entityFilter.getFilters()) {
509 String etfCondition = buildEtfCondition(ctx, etf, entityFilter.getDirection(), entityTypeFilterIdx++); 520 String etfCondition = buildEtfCondition(ctx, etf, entityFilter.getDirection(), entityTypeFilterIdx++);
510 if (!etfCondition.isEmpty()) { 521 if (!etfCondition.isEmpty()) {
511 if (noConditions) { 522 if (noConditions) {
512 - whereFilter.append(" WHERE ");  
513 noConditions = false; 523 noConditions = false;
514 } else { 524 } else {
515 whereFilter.append(" OR "); 525 whereFilter.append(" OR ");
@@ -525,15 +535,33 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -525,15 +535,33 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
525 } 535 }
526 } 536 }
527 if (noConditions) { 537 if (noConditions) {
528 - whereFilter.append(" WHERE re.") 538 + whereFilter.append(" re.")
529 .append(entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from") 539 .append(entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from")
530 .append("_type in (:where_entity_types").append(")"); 540 .append("_type in (:where_entity_types").append(")");
531 ctx.addStringListParameter("where_entity_types", Arrays.stream(RELATION_QUERY_ENTITY_TYPES).map(EntityType::name).collect(Collectors.toList())); 541 ctx.addStringListParameter("where_entity_types", Arrays.stream(RELATION_QUERY_ENTITY_TYPES).map(EntityType::name).collect(Collectors.toList()));
532 } 542 }
  543 +
  544 + if (!noConditions && !single) {
  545 + whereFilter = new StringBuilder().append("(").append(whereFilter).append(")");
  546 + }
  547 +
533 if (entityFilter.isFetchLastLevelOnly()) { 548 if (entityFilter.isFetchLastLevelOnly()) {
534 - whereFilter.append(" and re.lvl = ").append(entityFilter.getMaxLevel()); 549 + String toOrFrom = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from");
  550 + String fromOrTo = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "from" : "to");
  551 +
  552 + StringBuilder notExistsPart = new StringBuilder();
  553 + notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr WHERE ");
  554 + notExistsPart.append(whereFilter.toString());
  555 + notExistsPart
  556 + .append(" and ")
  557 + .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id")
  558 + .append(" and ")
  559 + .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type");
  560 +
  561 + notExistsPart.append(")");
  562 + whereFilter.append(" and ( re.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")");
535 } 563 }
536 - from = String.format(from, lvlFilter, whereFilter); 564 + from = String.format(from, lvlFilter, " WHERE " + whereFilter);
537 return "( " + selectFields + from + ")"; 565 return "( " + selectFields + from + ")";
538 } 566 }
539 567
@@ -18,7 +18,7 @@ package org.thingsboard.server.dao.util.mapping; @@ -18,7 +18,7 @@ package org.thingsboard.server.dao.util.mapping;
18 import com.fasterxml.jackson.core.JsonProcessingException; 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 import com.fasterxml.jackson.databind.JsonNode; 19 import com.fasterxml.jackson.databind.JsonNode;
20 import com.fasterxml.jackson.databind.ObjectMapper; 20 import com.fasterxml.jackson.databind.ObjectMapper;
21 -import org.thingsboard.server.common.data.alarm.Alarm; 21 +import com.fasterxml.jackson.databind.node.ObjectNode;
22 22
23 import java.io.IOException; 23 import java.io.IOException;
24 24
@@ -66,12 +66,16 @@ public class JacksonUtil { @@ -66,12 +66,16 @@ public class JacksonUtil {
66 throw new IllegalArgumentException(e); 66 throw new IllegalArgumentException(e);
67 } 67 }
68 } 68 }
  69 +
  70 + public static ObjectNode newObjectNode(){
  71 + return OBJECT_MAPPER.createObjectNode();
  72 + }
69 73
70 public static <T> T clone(T value) { 74 public static <T> T clone(T value) {
71 return fromString(toString(value), (Class<T>) value.getClass()); 75 return fromString(toString(value), (Class<T>) value.getClass());
72 } 76 }
73 77
74 - public static <T> JsonNode valueToTree(T alarm) {  
75 - return OBJECT_MAPPER.valueToTree(alarm); 78 + public static <T> JsonNode valueToTree(T value) {
  79 + return OBJECT_MAPPER.valueToTree(value);
76 } 80 }
77 } 81 }