Commit c4b89e29efab81c00b3f8eb6705eb3b3d46479a6

Authored by Igor Kulikov
2 parents 305ec306 77d754ad

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

... ... @@ -8,7 +8,7 @@
8 8 "configuration": null
9 9 },
10 10 "metadata": {
11   - "firstNodeIndex": 2,
  11 + "firstNodeIndex": 6,
12 12 "nodes": [
13 13 {
14 14 "additionalInfo": {
... ... @@ -82,10 +82,29 @@
82 82 "configuration": {
83 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 101 "connections": [
88 102 {
  103 + "fromIndex": 6,
  104 + "toIndex": 2,
  105 + "type": "Success"
  106 + },
  107 + {
89 108 "fromIndex": 2,
90 109 "toIndex": 4,
91 110 "type": "Other"
... ...
... ... @@ -181,6 +181,7 @@ public class ThingsboardInstallService {
181 181 databaseTsUpgradeService.upgradeDatabase("3.1.1");
182 182 }
183 183 databaseEntitiesUpgradeService.upgradeDatabase("3.1.1");
  184 + dataUpdateService.updateData("3.1.1");
184 185 log.info("Updating system data...");
185 186 systemDataLoaderService.updateSystemWidgets();
186 187 break;
... ...
... ... @@ -15,6 +15,8 @@
15 15 */
16 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 20 import com.google.common.util.concurrent.FutureCallback;
19 21 import com.google.common.util.concurrent.Futures;
20 22 import com.google.common.util.concurrent.ListenableFuture;
... ... @@ -23,9 +25,13 @@ import lombok.extern.slf4j.Slf4j;
23 25 import org.springframework.beans.factory.annotation.Autowired;
24 26 import org.springframework.context.annotation.Profile;
25 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 31 import org.thingsboard.server.common.data.EntityView;
27 32 import org.thingsboard.server.common.data.SearchTextBased;
28 33 import org.thingsboard.server.common.data.Tenant;
  34 +import org.thingsboard.server.common.data.id.EntityId;
29 35 import org.thingsboard.server.common.data.id.EntityViewId;
30 36 import org.thingsboard.server.common.data.id.TenantId;
31 37 import org.thingsboard.server.common.data.id.UUIDBased;
... ... @@ -35,10 +41,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
35 41 import org.thingsboard.server.common.data.page.PageData;
36 42 import org.thingsboard.server.common.data.page.PageLink;
37 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 46 import org.thingsboard.server.dao.entityview.EntityViewService;
39 47 import org.thingsboard.server.dao.rule.RuleChainService;
40 48 import org.thingsboard.server.dao.tenant.TenantService;
41 49 import org.thingsboard.server.dao.timeseries.TimeseriesService;
  50 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
42 51 import org.thingsboard.server.service.install.InstallScripts;
43 52
44 53 import javax.annotation.Nullable;
... ... @@ -49,6 +58,7 @@ import java.util.concurrent.ExecutionException;
49 58 import java.util.stream.Collectors;
50 59
51 60 import static org.apache.commons.lang.StringUtils.isBlank;
  61 +import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
52 62
53 63 @Service
54 64 @Profile("install")
... ... @@ -81,6 +91,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
81 91 log.info("Updating data from version 3.0.1 to 3.1.0 ...");
82 92 tenantsEntityViewsUpdater.updateEntities(null);
83 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 98 default:
85 99 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
86 100 }
... ... @@ -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 178 private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater =
111 179 new PaginatedUpdater<String, Tenant>() {
112 180
... ... @@ -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 217 private ListenableFuture<List<Void>> updateEntityViewLatestTelemetry(EntityView entityView) {
150 218 EntityViewId entityId = entityView.getId();
... ... @@ -160,13 +228,13 @@ public class DefaultDataUpdateService implements DataUpdateService {
160 228 keysFuture = Futures.immediateFuture(keys);
161 229 }
162 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 238 return Futures.transformAsync(latestFuture, latestValues -> {
171 239 if (latestValues != null && !latestValues.isEmpty()) {
172 240 ListenableFuture<List<Void>> saveFuture = tsService.saveLatest(TenantId.SYS_TENANT_ID, entityId, latestValues);
... ...
... ... @@ -467,9 +467,20 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
467 467 ctx.addStringParameter("where_relation_type", entityFilter.getRelationType());
468 468 whereFilter += " re.relation_type = :where_relation_type AND";
469 469 }
  470 + String toOrFrom = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from");
470 471 whereFilter += " re." + (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from") + "_type = :where_entity_type";
471 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 485 from = String.format(from, lvlFilter, whereFilter);
475 486 String query = "( " + selectFields + from + ")";
... ... @@ -502,14 +513,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
502 513 StringBuilder whereFilter = new StringBuilder();
503 514
504 515 boolean noConditions = true;
  516 + boolean single = entityFilter.getFilters() != null && entityFilter.getFilters().size() == 1;
505 517 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) {
506   - boolean single = entityFilter.getFilters().size() == 1;
507 518 int entityTypeFilterIdx = 0;
508 519 for (EntityTypeFilter etf : entityFilter.getFilters()) {
509 520 String etfCondition = buildEtfCondition(ctx, etf, entityFilter.getDirection(), entityTypeFilterIdx++);
510 521 if (!etfCondition.isEmpty()) {
511 522 if (noConditions) {
512   - whereFilter.append(" WHERE ");
513 523 noConditions = false;
514 524 } else {
515 525 whereFilter.append(" OR ");
... ... @@ -525,15 +535,33 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
525 535 }
526 536 }
527 537 if (noConditions) {
528   - whereFilter.append(" WHERE re.")
  538 + whereFilter.append(" re.")
529 539 .append(entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from")
530 540 .append("_type in (:where_entity_types").append(")");
531 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 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 565 return "( " + selectFields + from + ")";
538 566 }
539 567
... ...
... ... @@ -18,7 +18,7 @@ package org.thingsboard.server.dao.util.mapping;
18 18 import com.fasterxml.jackson.core.JsonProcessingException;
19 19 import com.fasterxml.jackson.databind.JsonNode;
20 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 23 import java.io.IOException;
24 24
... ... @@ -66,12 +66,16 @@ public class JacksonUtil {
66 66 throw new IllegalArgumentException(e);
67 67 }
68 68 }
  69 +
  70 + public static ObjectNode newObjectNode(){
  71 + return OBJECT_MAPPER.createObjectNode();
  72 + }
69 73
70 74 public static <T> T clone(T value) {
71 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 }
... ...