From 72a202dd5bd0004f7bb8d440ce2feea02229b2ce Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Fri, 9 Mar 2018 19:51:03 +0200 Subject: [PATCH 01/75] opc fix --- .../extensions-forms/extension-form-opc.directive.js | 4 ++-- .../extension/extensions-forms/extension-form-opc.tpl.html | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js index bf4886c994..967c561eb9 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js @@ -129,7 +129,7 @@ export default function ExtensionFormOpcDirective($compile, $templateCache, $tra if (addedFile && addedFile.length > 0) { model[options.fileName] = $file.name; - model[options.file] = addedFile.replace(/^data.*base64,/, ""); + model[options.location] = addedFile.replace(/^data.*base64,/, ""); } } @@ -143,7 +143,7 @@ export default function ExtensionFormOpcDirective($compile, $templateCache, $tra scope.theForm.$setDirty(); model[options.fileName] = null; - model[options.file] = null; + model[options.location] = null; }; diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index 501eeebd07..99464cc038 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -212,8 +212,8 @@ -
+
{{node.icon}}
- {{ node.type }} + {{ node.component.name }} {{ node.name }}
From c5b03cb5b38d8a5ff935c797ff515cea1d11214b Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 21 Mar 2018 19:40:25 +0200 Subject: [PATCH 22/75] Fix for RPC calls in Kafka actions --- .../server/common/msg/core/ToServerRpcRequestMsg.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java index ace51c03a7..87708a759f 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java @@ -16,16 +16,16 @@ package org.thingsboard.server.common.msg.core; import lombok.Data; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.common.msg.session.MsgType; /** * @author Andrew Shvayka */ @Data -public class ToServerRpcRequestMsg implements FromDeviceMsg { +public class ToServerRpcRequestMsg implements FromDeviceRequestMsg { - private final int requestId; + private final Integer requestId; private final String method; private final String params; From f8ec713d4a36446ebb383af81ba800f065d1f7d4 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Thu, 22 Mar 2018 10:53:50 +0200 Subject: [PATCH 23/75] update description in Node Annotations --- .../rule/engine/api/EnrichmentNode.java | 11 +++++++++-- .../thingsboard/rule/engine/api/FilterNode.java | 11 +++++++++-- .../rule/engine/api/TransformationNode.java | 8 ++++++++ .../rule/engine/filter/TbJsFilterNode.java | 7 ++++++- .../rule/engine/filter/TbJsSwitchNode.java | 7 ++++++- .../rule/engine/filter/TbMsgTypeFilterNode.java | 5 ++++- .../rule/engine/metadata/TbGetAttributesNode.java | 14 +++++++------- .../metadata/TbGetCustomerAttributeNode.java | 6 +++++- .../engine/metadata/TbGetRelatedAttributeNode.java | 8 +++++++- .../engine/metadata/TbGetTenantAttributeNode.java | 6 +++++- .../engine/transform/TbChangeOriginatorNode.java | 5 ++++- .../rule/engine/transform/TbTransformMsgNode.java | 6 +++++- 12 files changed, 75 insertions(+), 19 deletions(-) diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java index 2267bdae67..b3ff2559eb 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,11 +31,19 @@ public @interface EnrichmentNode { String name(); + String nodeDescription(); + + String nodeDetails(); + + boolean inEnabled() default true; + + boolean outEnabled() default true; + ComponentScope scope() default ComponentScope.TENANT; String descriptor() default "EmptyNodeDescriptor.json"; - String[] relationTypes() default {"Success","Failure"}; + String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java index 5247e397dd..d34755dc2c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,11 +31,19 @@ public @interface FilterNode { String name(); + String nodeDescription(); + + String nodeDetails(); + + boolean inEnabled() default true; + + boolean outEnabled() default true; + ComponentScope scope() default ComponentScope.TENANT; String descriptor() default "EmptyNodeDescriptor.json"; - String[] relationTypes() default {"Success","Failure"}; + String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java index bfe1dcaeda..76b0dda01e 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java @@ -32,6 +32,14 @@ public @interface TransformationNode { String name(); + String nodeDescription(); + + String nodeDetails(); + + boolean inEnabled() default true; + + boolean outEnabled() default true; + ComponentScope scope() default ComponentScope.TENANT; String descriptor() default "EmptyNodeDescriptor.json"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index d3fee6859e..393496a41e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -26,7 +26,12 @@ import javax.script.Bindings; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "Filter Node", relationTypes = {"True", "False", "Failure"}) +@FilterNode(name = "script", relationTypes = {"True", "False", "Failure"}, + nodeDescription = "Filter incoming messages using JS script", + nodeDetails = "Evaluate incoming Message with configured JS condition. " + + "If 'True' - send Message via 'True' chain, otherwise 'False' chain is used." + + "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;'" + + "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';'") public class TbJsFilterNode implements TbNode { private TbJsFilterNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 5c7d1d10f4..dff214567d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -27,7 +27,12 @@ import java.util.Set; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "Switch Node", customRelations = true) +@FilterNode(name = "switch", customRelations = true, + nodeDescription = "Route incoming Message to one or multiple output chains", + nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + + "If Array is empty - message not routed to next Node. " + + "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;' " + + "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';' ") public class TbJsSwitchNode implements TbNode { private TbJsSwitchNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 381512a806..c08c3e0bf5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -24,7 +24,10 @@ import org.thingsboard.server.common.msg.TbMsg; * Created by ashvayka on 19.01.18. */ @Slf4j -@FilterNode(name = "Message Type Filter Node") +@FilterNode(name = "message type", + nodeDescription = "Filter incoming messages by Message Type", + nodeDetails = "Evaluate incoming Message with configured JS condition. " + + "If incoming MessageType is expected - send Message via 'Success' chain, otherwise 'Failure' chain is used.") public class TbMsgTypeFilterNode implements TbNode { TbMsgTypeFilterNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 90eadcb2c7..216cf28015 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -21,12 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeState; -import org.thingsboard.rule.engine.api.TbNode; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; @@ -40,7 +35,12 @@ import static org.thingsboard.server.common.data.DataConstants.*; * Created by ashvayka on 19.01.18. */ @Slf4j -@EnrichmentNode(name = "Get Attributes Node") +@EnrichmentNode(name = "originator attributes", + nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + + "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + + "'meta.cs.temperature' or 'meta.shared.limit' " + + "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.") public class TbGetAttributesNode implements TbNode { private TbGetAttributesNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 18ddfcf6b9..c9cabaaabc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -22,7 +22,11 @@ import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; -@EnrichmentNode(name="Get Customer Attributes Node") +@EnrichmentNode(name="customer attributes", + nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + + "To access those attributes in other nodes this template can be used " + + "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 3a0dce85a2..f85d019b04 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -26,7 +26,13 @@ import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; -@EnrichmentNode(name="Get Related Entity Attributes Node") +@EnrichmentNode(name="related attributes", + nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + + "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + + "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + + "To access those attributes in other nodes this template can be used " + + "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { private TbGetRelatedAttrNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index e51c053ca2..2603bc5cc1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -24,7 +24,11 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @Slf4j -@EnrichmentNode(name="Get Tenant Attributes Node") +@EnrichmentNode(name="tenant attributes", + nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + + "To access those attributes in other nodes this template can be used " + + "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 36063b7fc2..8ab8e18abc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -32,7 +32,10 @@ import org.thingsboard.server.common.msg.TbMsg; import java.util.HashSet; @Slf4j -@EnrichmentNode(name = "Change Originator Node") +@TransformationNode(name="change originator", + nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", + 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. ") public class TbChangeOriginatorNode extends TbAbstractTransformNode { protected static final String CUSTOMER_SOURCE = "CUSTOMER"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index c0d20f43c4..3cd7cd1374 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -23,7 +23,11 @@ import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; -@EnrichmentNode(name = "Transformation Node") +@TransformationNode(name = "script", + nodeDescription = "Change Message payload and Metadata using JavaScript", + nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside. " + + "'meta' - is a Message metadata. " + + "'msg' - is a Message payload. Any properties can be changed/removed/added in those objects.") public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; From dff2ca0cb18fe5e03e95fa76dcb9f2ec465fd96f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Mar 2018 11:15:22 +0200 Subject: [PATCH 24/75] Rule Node tooltips --- ui/src/app/rulechain/rulechain.controller.js | 82 +++++++++++++++++++- ui/src/app/rulechain/rulechain.tpl.html | 8 +- ui/src/app/rulechain/rulenode.tpl.html | 5 +- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 31886b8a28..76d0fec0c5 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -16,6 +16,10 @@ import './rulechain.scss'; +import 'tooltipster/dist/css/tooltipster.bundle.min.css'; +import 'tooltipster/dist/js/tooltipster.bundle.min.js'; +import 'tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css'; + /* eslint-disable import/no-unresolved, import/default */ import addRuleNodeTemplate from './add-rulenode.tpl.html'; @@ -30,7 +34,7 @@ const aKeyCode = 65; const escKeyCode = 27; /*@ngInject*/ -export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { +export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { var vm = this; @@ -139,6 +143,82 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans vm.editingRuleNodeLink = angular.copy(edge); }; + vm.nodeLibCallbacks = { + nodeCallbacks: { + 'mouseEnter': function (event, node) { + displayNodeDescriptionTooltip(event, node); + }, + 'mouseLeave': function () { + destroyTooltips(); + } + } + }; + + vm.typeHeaderMouseEnter = function(event, typeId) { + displayTooltip(event, + '
' + + '
' + + '
' + typeId + '
' + + '
' + 'Some description of component type' + '
' + + '
' + + '
' + ); + }; + + vm.destroyTooltips = destroyTooltips; + + function destroyTooltips() { + if (vm.tooltipTimeout) { + $timeout.cancel(vm.tooltipTimeout); + vm.tooltipTimeout = null; + } + var instances = angular.element.tooltipster.instances(); + instances.forEach((instance) => { + instance.destroy(); + }); + } + + function displayNodeDescriptionTooltip(event, node) { + displayTooltip(event, + '
' + + '
' + + '
' + node.component.name + '
' + + '
' + 'Some description of node' + '
' + + '
' + + '
' + ); + } + + function displayTooltip(event, content) { + destroyTooltips(); + vm.tooltipTimeout = $timeout(() => { + var element = angular.element(event.target); + element.tooltipster( + { + theme: 'tooltipster-shadow', + delay: 100, + trigger: 'custom', + triggerOpen: { + click: false, + tap: false + }, + triggerClose: { + click: true, + tap: true, + scroll: true + }, + side: 'right', + trackOrigin: true + } + ); + var contentElement = angular.element(content); + $compile(contentElement)($scope); + var tooltip = element.tooltipster('instance'); + tooltip.content(contentElement); + tooltip.open(); + }, 500); + } + vm.editCallbacks = { edgeDoubleClick: function (event, edge) { var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html index 30f2caa91f..9f1141e887 100644 --- a/ui/src/app/rulechain/rulechain.tpl.html +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -23,12 +23,15 @@
- +
{{vm.types.ruleNodeType[typeId].name}}
- +
{{vm.types.ruleNodeType[typeId].name}}
@@ -36,6 +39,7 @@ diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html index f8e08e0d2c..5a521a8333 100644 --- a/ui/src/app/rulechain/rulenode.tpl.html +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -18,7 +18,10 @@
+ ng-dblclick="callbacks.doubleClick($event, node)" + ng-mouseover="callbacks.mouseOver($event, node)" + ng-mouseenter="callbacks.mouseEnter($event, node)" + ng-mouseleave="callbacks.mouseLeave($event, node)">
{{node.icon}} From 8f3fbc945dd7c33c1eb1305e499e07725d2c0570 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Mar 2018 13:16:28 +0200 Subject: [PATCH 25/75] RuleNode annotation refactoring --- .../AnnotationComponentDiscoveryService.java | 90 ++++++++++++------- .../rule/engine/api/ActionNode.java | 43 --------- .../rule/engine/api/EnrichmentNode.java | 49 ---------- .../rule/engine/api/NodeDefinition.java | 33 +++++++ .../api/{FilterNode.java => RuleNode.java} | 11 ++- .../rule/engine/api/TransformationNode.java | 51 ----------- .../src/main/resources/EmptyNodeConfig.json | 2 + .../rule/engine/filter/TbJsFilterNode.java | 11 ++- .../rule/engine/filter/TbJsSwitchNode.java | 9 +- .../engine/filter/TbMsgTypeFilterNode.java | 7 +- .../engine/metadata/TbGetAttributesNode.java | 12 +-- .../metadata/TbGetCustomerAttributeNode.java | 9 +- .../metadata/TbGetRelatedAttributeNode.java | 13 ++- .../metadata/TbGetTenantAttributeNode.java | 9 +- .../transform/TbChangeOriginatorNode.java | 5 +- .../engine/transform/TbTransformMsgNode.java | 11 ++- ui/src/app/common/types.constant.js | 15 +++- ui/src/app/rulechain/rulechain.controller.js | 3 +- ui/src/app/rulechain/rulechain.scss | 29 ++++++ 19 files changed, 194 insertions(+), 218 deletions(-) delete mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java delete mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java rename rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/{FilterNode.java => RuleNode.java} (87%) delete mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java create mode 100644 rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index d9416d7226..52d7d7d80c 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -16,6 +16,8 @@ package org.thingsboard.server.service.component; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Charsets; import com.google.common.io.Resources; import lombok.extern.slf4j.Slf4j; @@ -26,16 +28,14 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen import org.springframework.core.env.Environment; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.ActionNode; -import org.thingsboard.rule.engine.api.EnrichmentNode; -import org.thingsboard.rule.engine.api.FilterNode; -import org.thingsboard.rule.engine.api.TransformationNode; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.dao.component.ComponentDescriptorService; import org.thingsboard.server.extensions.api.component.*; import javax.annotation.PostConstruct; +import java.io.IOException; import java.lang.annotation.Annotation; import java.util.*; import java.util.stream.Collectors; @@ -70,6 +70,24 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe } } + private void registerRuleNodeComponents() { + Set ruleNodeBeanDefinitions = getBeanDefinitions(RuleNode.class); + for (BeanDefinition def : ruleNodeBeanDefinitions) { + try { + String clazzName = def.getBeanClassName(); + Class clazz = Class.forName(clazzName); + RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); + ComponentType type = ruleNodeAnnotation.type(); + ComponentDescriptor component = scanAndPersistComponent(def, type); + components.put(component.getClazz(), component); + componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component); + } catch (Exception e) { + log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e); + throw new RuntimeException(e); + } + } + } + private void registerComponents(ComponentType type, Class annotation) { List components = persist(getBeanDefinitions(annotation), type); componentsMap.put(type, components); @@ -97,34 +115,25 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe String descriptorResourceName; switch (type) { case ENRICHMENT: - EnrichmentNode enrichmentAnnotation = clazz.getAnnotation(EnrichmentNode.class); - scannedComponent.setName(enrichmentAnnotation.name()); - scannedComponent.setScope(enrichmentAnnotation.scope()); - descriptorResourceName = enrichmentAnnotation.descriptor(); - break; case FILTER: - FilterNode filterAnnotation = clazz.getAnnotation(FilterNode.class); - scannedComponent.setName(filterAnnotation.name()); - scannedComponent.setScope(filterAnnotation.scope()); - descriptorResourceName = filterAnnotation.descriptor(); - break; case TRANSFORMATION: - TransformationNode trAnnotation = clazz.getAnnotation(TransformationNode.class); - scannedComponent.setName(trAnnotation.name()); - scannedComponent.setScope(trAnnotation.scope()); - descriptorResourceName = trAnnotation.descriptor(); - break; case ACTION: - ActionNode actionAnnotation = clazz.getAnnotation(ActionNode.class); - scannedComponent.setName(actionAnnotation.name()); - scannedComponent.setScope(actionAnnotation.scope()); - descriptorResourceName = actionAnnotation.descriptor(); + RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); + scannedComponent.setName(ruleNodeAnnotation.name()); + scannedComponent.setScope(ruleNodeAnnotation.scope()); + NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation); + ObjectNode configurationDescriptor = mapper.createObjectNode(); + JsonNode node = mapper.valueToTree(nodeDefinition); + configurationDescriptor.set("nodeDefinition", node); + scannedComponent.setConfigurationDescriptor(configurationDescriptor); break; case OLD_ACTION: Action oldActionAnnotation = clazz.getAnnotation(Action.class); scannedComponent.setName(oldActionAnnotation.name()); scannedComponent.setScope(oldActionAnnotation.scope()); descriptorResourceName = oldActionAnnotation.descriptor(); + scannedComponent.setConfigurationDescriptor(mapper.readTree( + Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); break; case PLUGIN: Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class); @@ -143,12 +152,12 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe } } scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(Class::getName).collect(Collectors.joining(","))); + scannedComponent.setConfigurationDescriptor(mapper.readTree( + Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); break; default: throw new RuntimeException(type + " is not supported yet!"); } - scannedComponent.setConfigurationDescriptor(mapper.readTree( - Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); scannedComponent.setClazz(clazzName); log.info("Processing scanned component: {}", scannedComponent); } catch (Exception e) { @@ -171,6 +180,20 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return scannedComponent; } + private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws IOException { + NodeDefinition nodeDefinition = new NodeDefinition(); + nodeDefinition.setDetails(nodeAnnotation.nodeDetails()); + nodeDefinition.setDescription(nodeAnnotation.nodeDescription()); + nodeDefinition.setInEnabled(nodeAnnotation.inEnabled()); + nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); + nodeDefinition.setRelationTypes(nodeAnnotation.relationTypes()); + nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); + String defaultConfigResourceName = nodeAnnotation.defaultConfigResource(); + nodeDefinition.setDefaultConfiguration(mapper.readTree( + Resources.toString(Resources.getResource(defaultConfigResourceName), Charsets.UTF_8))); + return nodeDefinition; + } + private Set getBeanDefinitions(Class componentType) { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(componentType)); @@ -183,13 +206,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @Override public void discoverComponents() { - registerComponents(ComponentType.ENRICHMENT, EnrichmentNode.class); - - registerComponents(ComponentType.FILTER, FilterNode.class); - - registerComponents(ComponentType.TRANSFORMATION, TransformationNode.class); - registerComponents(ComponentType.ACTION, ActionNode.class); + registerRuleNodeComponents(); registerComponents(ComponentType.OLD_ACTION, Action.class); @@ -200,15 +218,19 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @Override public List getComponents(ComponentType type) { - return Collections.unmodifiableList(componentsMap.get(type)); + if (componentsMap.containsKey(type)) { + return Collections.unmodifiableList(componentsMap.get(type)); + } else { + return Collections.emptyList(); + } } @Override public List getComponents(Set types) { List result = new ArrayList<>(); - for (ComponentType type : types) { + types.stream().filter(type -> componentsMap.containsKey(type)).forEach(type -> { result.addAll(componentsMap.get(type)); - } + }); return Collections.unmodifiableList(result); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java deleted file mode 100644 index 64e28f1bf2..0000000000 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java +++ /dev/null @@ -1,43 +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.api; - -import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Andrew Shvayka - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface ActionNode { - - String name(); - - ComponentScope scope() default ComponentScope.TENANT; - - String descriptor() default "EmptyNodeDescriptor.json"; - - String[] relationTypes() default {"Success","Failure"}; - - boolean customRelations() default false; - -} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java deleted file mode 100644 index b3ff2559eb..0000000000 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java +++ /dev/null @@ -1,49 +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.api; - -import org.thingsboard.server.common.data.plugin.ComponentScope; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Andrew Shvayka - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface EnrichmentNode { - - String name(); - - String nodeDescription(); - - String nodeDetails(); - - boolean inEnabled() default true; - - boolean outEnabled() default true; - - ComponentScope scope() default ComponentScope.TENANT; - - String descriptor() default "EmptyNodeDescriptor.json"; - - String[] relationTypes() default {"Success", "Failure"}; - - boolean customRelations() default false; -} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java new file mode 100644 index 0000000000..6c57d92a54 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java @@ -0,0 +1,33 @@ +/** + * 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.api; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; + +@Data +public class NodeDefinition { + + private String details; + private String description; + private boolean inEnabled; + private boolean outEnabled; + String[] relationTypes; + boolean customRelations; + JsonNode defaultConfiguration; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java similarity index 87% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java index d34755dc2c..f8e0fa2e29 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java @@ -16,18 +16,18 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.data.plugin.ComponentScope; +import org.thingsboard.server.common.data.plugin.ComponentType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * @author Andrew Shvayka - */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface FilterNode { +public @interface RuleNode { + + ComponentType type(); String name(); @@ -41,10 +41,9 @@ public @interface FilterNode { ComponentScope scope() default ComponentScope.TENANT; - String descriptor() default "EmptyNodeDescriptor.json"; + String defaultConfigResource() default "EmptyNodeConfig.json"; String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; - } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java deleted file mode 100644 index 76b0dda01e..0000000000 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java +++ /dev/null @@ -1,51 +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.api; - -import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Andrew Shvayka - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface TransformationNode { - - String name(); - - String nodeDescription(); - - String nodeDetails(); - - boolean inEnabled() default true; - - boolean outEnabled() default true; - - ComponentScope scope() default ComponentScope.TENANT; - - String descriptor() default "EmptyNodeDescriptor.json"; - - String[] relationTypes() default {"Success","Failure"}; - - boolean customRelations() default false; - -} diff --git a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json new file mode 100644 index 0000000000..7a73a41bfd --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 393496a41e..c85d4807e0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; @@ -26,12 +27,14 @@ import javax.script.Bindings; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "script", relationTypes = {"True", "False", "Failure"}, +@RuleNode( + type = ComponentType.FILTER, + name = "script", relationTypes = {"True", "False", "Failure"}, nodeDescription = "Filter incoming messages using JS script", nodeDetails = "Evaluate incoming Message with configured JS condition. " + - "If 'True' - send Message via 'True' chain, otherwise 'False' chain is used." + - "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;'" + - "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';'") + "If True - send Message via True chain, otherwise False chain is used." + + "Message payload can be accessed via msg property. For example msg.temperature < 10;" + + "Message metadata can be accessed via meta property. For example meta.customerName === 'John';") public class TbJsFilterNode implements TbNode { private TbJsFilterNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index dff214567d..faf97b4e50 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; @@ -27,12 +28,14 @@ import java.util.Set; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "switch", customRelations = true, +@RuleNode( + type = ComponentType.FILTER, + name = "switch", customRelations = true, nodeDescription = "Route incoming Message to one or multiple output chains", nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + "If Array is empty - message not routed to next Node. " + - "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;' " + - "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';' ") + "Message payload can be accessed via msg property. For example msg.temperature < 10; " + + "Message metadata can be accessed via meta property. For example meta.customerName === 'John';") public class TbJsSwitchNode implements TbNode { private TbJsSwitchNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index c08c3e0bf5..7a6f9fdbee 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -18,16 +18,19 @@ package org.thingsboard.rule.engine.filter; import lombok.extern.slf4j.Slf4j; 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; /** * Created by ashvayka on 19.01.18. */ @Slf4j -@FilterNode(name = "message type", +@RuleNode( + type = ComponentType.FILTER, + name = "message type", nodeDescription = "Filter incoming messages by Message Type", nodeDetails = "Evaluate incoming Message with configured JS condition. " + - "If incoming MessageType is expected - send Message via 'Success' chain, otherwise 'Failure' chain is used.") + "If incoming MessageType is expected - send Message via Success chain, otherwise Failure chain is used.") public class TbMsgTypeFilterNode implements TbNode { TbMsgTypeFilterNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 216cf28015..6228206da8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; @@ -35,11 +36,12 @@ import static org.thingsboard.server.common.data.DataConstants.*; * Created by ashvayka on 19.01.18. */ @Slf4j -@EnrichmentNode(name = "originator attributes", - nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", - nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + - "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + - "'meta.cs.temperature' or 'meta.shared.limit' " + +@RuleNode(type = ComponentType.ENRICHMENT, + name = "originator attributes", + nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + + "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + + "meta.cs.temperature or meta.shared.limit " + "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.") public class TbGetAttributesNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index c9cabaaabc..d85fb56de0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -16,17 +16,20 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.plugin.ComponentType; -@EnrichmentNode(name="customer attributes", +@RuleNode( + type = ComponentType.ENRICHMENT, + name="customer attributes", nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "meta.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index f85d019b04..26b756176c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -17,22 +17,21 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeState; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.plugin.ComponentType; -@EnrichmentNode(name="related attributes", +@RuleNode( + type = ComponentType.ENRICHMENT, + name="related attributes", nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "meta.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { private TbGetRelatedAttrNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index 2603bc5cc1..7d9c50bfc6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -17,18 +17,21 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentType; @Slf4j -@EnrichmentNode(name="tenant attributes", +@RuleNode( + type = ComponentType.ENRICHMENT, + name="tenant attributes", nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "meta.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 8ab8e18abc..d237df83ce 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -27,12 +27,15 @@ import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import java.util.HashSet; @Slf4j -@TransformationNode(name="change originator", +@RuleNode( + type = ComponentType.TRANSFORMATION, + name="change originator", nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", 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. ") diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 3cd7cd1374..babdbc3085 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -19,15 +19,18 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; -@TransformationNode(name = "script", +@RuleNode( + type = ComponentType.TRANSFORMATION, + name = "script", nodeDescription = "Change Message payload and Metadata using JavaScript", - nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside. " + - "'meta' - is a Message metadata. " + - "'msg' - is a Message payload. Any properties can be changed/removed/added in those objects.") + nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.
" + + "meta - is a Message metadata.
" + + "msg - is a Message payload.
Any properties can be changed/removed/added in those objects.") public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 2f8d152029..4dd961a11f 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -460,8 +460,19 @@ export default angular.module('thingsboard.types', []) ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"], ruleChainNodeComponent: { type: 'RULE_CHAIN', - name: 'Rule chain', - clazz: 'tb.internal.RuleChain' + name: 'rule chain', + clazz: 'tb.internal.RuleChain', + configurationDescriptor: { + nodeDefinition: { + description: "Forwards incoming messages to specified Rule Chain", + details: "Forwards incoming messages to specified Rule Chain", + inEnabled: true, + outEnabled: false, + relationTypes: [], + customRelations: false, + defaultConfiguration: {} + } + } }, inputNodeComponent: { type: 'INPUT', diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 76d0fec0c5..973618de6b 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -183,7 +183,8 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, '
' + '
' + '
' + node.component.name + '
' + - '
' + 'Some description of node' + '
' + + '
' + node.component.configurationDescriptor.nodeDefinition.description + '
' + + '
' + node.component.configurationDescriptor.nodeDefinition.details + '
' + '
' + '
' ); diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 258e232ea4..26a5225a68 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -260,3 +260,32 @@ stroke-dashoffset: 500; } } + +.tb-rule-node-tooltip { + font-size: 14px; + width: 300px; + color: #333; + #tooltip-content { + .tb-node-title { + font-weight: 600; + } + .tb-node-description { + font-style: italic; + color: #555; + } + .tb-node-details { + padding-top: 10px; + padding-bottom: 10px; + } + code { + padding: 0px 3px 2px 3px; + margin: 1px; + color: #AD1625; + white-space: nowrap; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 2px; + font-size: 12px; + } + } +} \ No newline at end of file From 73473e3bebf1b62a26558ce30ba3ab4f9801a3a4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Mar 2018 13:48:46 +0200 Subject: [PATCH 26/75] Rule Chain: Update tooltip messages --- ui/src/app/common/types.constant.js | 7 ++++++- ui/src/app/locale/locale.constant.js | 7 ++++++- ui/src/app/rulechain/rulechain.controller.js | 8 +++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 4dd961a11f..8026115b35 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -464,7 +464,7 @@ export default angular.module('thingsboard.types', []) clazz: 'tb.internal.RuleChain', configurationDescriptor: { nodeDefinition: { - description: "Forwards incoming messages to specified Rule Chain", + description: "", details: "Forwards incoming messages to specified Rule Chain", inEnabled: true, outEnabled: false, @@ -483,30 +483,35 @@ export default angular.module('thingsboard.types', []) FILTER: { value: "FILTER", name: "rulenode.type-filter", + details: "rulenode.type-filter-details", nodeClass: "tb-filter-type", icon: "filter_list" }, ENRICHMENT: { value: "ENRICHMENT", name: "rulenode.type-enrichment", + details: "rulenode.type-enrichment-details", nodeClass: "tb-enrichment-type", icon: "playlist_add" }, TRANSFORMATION: { value: "TRANSFORMATION", name: "rulenode.type-transformation", + details: "rulenode.type-transformation-details", nodeClass: "tb-transformation-type", icon: "transform" }, ACTION: { value: "ACTION", name: "rulenode.type-action", + details: "rulenode.type-action-details", nodeClass: "tb-action-type", icon: "flash_on" }, RULE_CHAIN: { value: "RULE_CHAIN", name: "rulenode.type-rule-chain", + details: "rulenode.type-rule-chain-details", nodeClass: "tb-rule-chain-type", icon: "settings_ethernet" }, diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 18c9330165..360c8282ac 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1186,10 +1186,15 @@ export default angular.module('thingsboard.locale', []) "custom-link-label": "Custom link label", "custom-link-label-required": "Custom link label is required.", "type-filter": "Filter", + "type-filter-details": "Filter incoming messages with configured conditions", "type-enrichment": "Enrichment", + "type-enrichment-details": "Add additional information into Message Metadata", "type-transformation": "Transformation", + "type-transformation-details": "Change Message payload and Metadata", "type-action": "Action", - "type-rule-chain": "Rule Chain" + "type-action-details": "Perform special action", + "type-rule-chain": "Rule Chain", + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain" }, "rule-plugin": { "management": "Rules and plugins management" diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 973618de6b..d9bbf2fd8d 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -34,7 +34,8 @@ const aKeyCode = 65; const escKeyCode = 27; /*@ngInject*/ -export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { +export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, + $filter, $translate, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { var vm = this; @@ -155,11 +156,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }; vm.typeHeaderMouseEnter = function(event, typeId) { + var ruleNodeType = types.ruleNodeType[typeId]; displayTooltip(event, '
' + '
' + - '
' + typeId + '
' + - '
' + 'Some description of component type' + '
' + + '
' + $translate.instant(ruleNodeType.name) + '
' + + '
' + $translate.instant(ruleNodeType.details) + '
' + '
' + '
' ); From adb71ecfe7b2a87e372d207837c769017f49d18a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 23 Mar 2018 10:41:29 +0200 Subject: [PATCH 27/75] Rule Node Configuration --- .../AnnotationComponentDiscoveryService.java | 9 +- .../src/main/resources/thingsboard.yml | 2 +- .../rule/engine/api/NodeConfiguration.java | 22 +++ .../thingsboard/rule/engine/api/RuleNode.java | 5 +- .../src/main/resources/EmptyNodeConfig.json | 2 - .../main/resources/EmptyNodeDescriptor.json | 2 - .../rule/engine/filter/TbJsFilterNode.java | 1 + .../filter/TbJsFilterNodeConfiguration.java | 10 +- .../rule/engine/filter/TbJsSwitchNode.java | 1 + .../filter/TbJsSwitchNodeConfiguration.java | 17 +- .../engine/filter/TbMsgTypeFilterNode.java | 1 + .../TbMsgTypeFilterNodeConfiguration.java | 11 +- .../engine/metadata/TbGetAttributesNode.java | 1 + .../TbGetAttributesNodeConfiguration.java | 13 +- .../metadata/TbGetCustomerAttributeNode.java | 1 + .../TbGetEntityAttrNodeConfiguration.java | 14 +- .../TbGetRelatedAttrNodeConfiguration.java | 19 +- .../metadata/TbGetRelatedAttributeNode.java | 1 + .../metadata/TbGetTenantAttributeNode.java | 1 + .../transform/TbChangeOriginatorNode.java | 1 + .../TbChangeOriginatorNodeConfiguration.java | 14 +- .../engine/transform/TbTransformMsgNode.java | 1 + .../TbTransformMsgNodeConfiguration.java | 11 +- ui/src/app/api/rule-chain.service.js | 25 +-- .../components/json-object-edit.directive.js | 165 ++++++++++++++++++ ui/src/app/components/json-object-edit.scss | 35 ++++ .../app/components/json-object-edit.tpl.html | 34 ++++ ui/src/app/layout/index.js | 4 +- ui/src/app/locale/locale.constant.js | 1 + ui/src/app/rulechain/rulechain.controller.js | 82 ++++----- .../app/rulechain/rulenode-fieldset.tpl.html | 5 + ui/src/app/rulechain/rulenode.directive.js | 2 + ui/src/app/rulechain/rulenode.scss | 22 +++ ui/src/app/rulechain/rulenode.tpl.html | 2 +- ui/src/scss/main.scss | 18 ++ 35 files changed, 483 insertions(+), 72 deletions(-) create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java delete mode 100644 rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json delete mode 100644 rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json create mode 100644 ui/src/app/components/json-object-edit.directive.js create mode 100644 ui/src/app/components/json-object-edit.scss create mode 100644 ui/src/app/components/json-object-edit.tpl.html create mode 100644 ui/src/app/rulechain/rulenode.scss diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index 52d7d7d80c..479f424e93 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -180,7 +180,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return scannedComponent; } - private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws IOException { + private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws Exception { NodeDefinition nodeDefinition = new NodeDefinition(); nodeDefinition.setDetails(nodeAnnotation.nodeDetails()); nodeDefinition.setDescription(nodeAnnotation.nodeDescription()); @@ -188,9 +188,10 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); nodeDefinition.setRelationTypes(nodeAnnotation.relationTypes()); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); - String defaultConfigResourceName = nodeAnnotation.defaultConfigResource(); - nodeDefinition.setDefaultConfiguration(mapper.readTree( - Resources.toString(Resources.getResource(defaultConfigResourceName), Charsets.UTF_8))); + Class configClazz = nodeAnnotation.configClazz(); + NodeConfiguration config = configClazz.newInstance(); + NodeConfiguration defaultConfiguration = config.defaultConfiguration(); + nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); return nodeDefinition; } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 27585212db..1df2f1cf31 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -234,7 +234,7 @@ caffeine: specs: relations: timeToLiveInMinutes: 1440 - maxSize: 100000 + maxSize: 0 deviceCredentials: timeToLiveInMinutes: 1440 maxSize: 100000 diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java new file mode 100644 index 0000000000..5e4c4b5399 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java @@ -0,0 +1,22 @@ +/** + * 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.api; + +public interface NodeConfiguration { + + NodeConfiguration defaultConfiguration(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java index f8e0fa2e29..16170347a7 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java @@ -35,15 +35,16 @@ public @interface RuleNode { String nodeDetails(); + Class configClazz(); + boolean inEnabled() default true; boolean outEnabled() default true; ComponentScope scope() default ComponentScope.TENANT; - String defaultConfigResource() default "EmptyNodeConfig.json"; - String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; + } diff --git a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json deleted file mode 100644 index 7a73a41bfd..0000000000 --- a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json deleted file mode 100644 index 7a73a41bfd..0000000000 --- a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index c85d4807e0..07b166db68 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -30,6 +30,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @RuleNode( type = ComponentType.FILTER, name = "script", relationTypes = {"True", "False", "Failure"}, + configClazz = TbJsFilterNodeConfiguration.class, nodeDescription = "Filter incoming messages using JS script", nodeDetails = "Evaluate incoming Message with configured JS condition. " + "If True - send Message via True chain, otherwise False chain is used." + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java index bf543e3276..3b19c7c641 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java @@ -16,9 +16,17 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; @Data -public class TbJsFilterNodeConfiguration { +public class TbJsFilterNodeConfiguration implements NodeConfiguration { private String jsScript; + + @Override + public TbJsFilterNodeConfiguration defaultConfiguration() { + TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); + configuration.setJsScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;"); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index faf97b4e50..c1236a4653 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -31,6 +31,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @RuleNode( type = ComponentType.FILTER, name = "switch", customRelations = true, + configClazz = TbJsSwitchNodeConfiguration.class, nodeDescription = "Route incoming Message to one or multiple output chains", nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + "If Array is empty - message not routed to next Node. " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java index 331302d542..b354c7199f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java @@ -15,14 +15,29 @@ */ package org.thingsboard.rule.engine.filter; +import com.google.common.collect.Sets; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; import java.util.Set; @Data -public class TbJsSwitchNodeConfiguration { +public class TbJsSwitchNodeConfiguration implements NodeConfiguration { private String jsScript; private Set allowedRelations; private boolean routeToAllWithNoCheck; + + @Override + public TbJsSwitchNodeConfiguration defaultConfiguration() { + TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); + configuration.setJsScript("function nextRelation(meta, msg) {\n" + + " return ['one','nine'];" + + "};\n" + + "\n" + + "nextRelation(meta, msg);"); + configuration.setAllowedRelations(Sets.newHashSet("one", "two")); + configuration.setRouteToAllWithNoCheck(false); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 7a6f9fdbee..3a86c25afb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.msg.TbMsg; @RuleNode( type = ComponentType.FILTER, name = "message type", + configClazz = TbMsgTypeFilterNodeConfiguration.class, nodeDescription = "Filter incoming messages by Message Type", nodeDetails = "Evaluate incoming Message with configured JS condition. " + "If incoming MessageType is expected - send Message via Success chain, otherwise Failure chain is used.") diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java index 3b7ba9055e..a2e1b179f0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java @@ -16,15 +16,24 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import java.util.Arrays; +import java.util.Collections; import java.util.List; /** * Created by ashvayka on 19.01.18. */ @Data -public class TbMsgTypeFilterNodeConfiguration { +public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration { private List messageTypes; + @Override + public TbMsgTypeFilterNodeConfiguration defaultConfiguration() { + TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration(); + configuration.setMessageTypes(Arrays.asList("GET_ATTRIBUTES","POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST")); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 6228206da8..69ee9d7c79 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -38,6 +38,7 @@ import static org.thingsboard.server.common.data.DataConstants.*; @Slf4j @RuleNode(type = ComponentType.ENRICHMENT, name = "originator attributes", + configClazz = TbGetAttributesNodeConfiguration.class, nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java index ad92314324..103b4de956 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -16,14 +16,16 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import java.util.Collections; import java.util.List; /** * Created by ashvayka on 19.01.18. */ @Data -public class TbGetAttributesNodeConfiguration { +public class TbGetAttributesNodeConfiguration implements NodeConfiguration { private List clientAttributeNames; private List sharedAttributeNames; @@ -31,4 +33,13 @@ public class TbGetAttributesNodeConfiguration { private List latestTsKeyNames; + @Override + public TbGetAttributesNodeConfiguration defaultConfiguration() { + TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); + configuration.setClientAttributeNames(Collections.emptyList()); + configuration.setSharedAttributeNames(Collections.emptyList()); + configuration.setServerAttributeNames(Collections.emptyList()); + configuration.setLatestTsKeyNames(Collections.emptyList()); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index d85fb56de0..cc6d6a1197 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @RuleNode( type = ComponentType.ENRICHMENT, name="customer attributes", + configClazz = TbGetEntityAttrNodeConfiguration.class, nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java index a5e85c57d7..51951150af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java @@ -16,13 +16,25 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @Data -public class TbGetEntityAttrNodeConfiguration { +public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration { private Map attrMapping; private boolean isTelemetry = false; + + @Override + public TbGetEntityAttrNodeConfiguration defaultConfiguration() { + TbGetEntityAttrNodeConfiguration configuration = new TbGetEntityAttrNodeConfiguration(); + Map attrMapping = new HashMap<>(); + attrMapping.putIfAbsent("temperature", "tempo"); + configuration.setAttrMapping(attrMapping); + configuration.setTelemetry(true); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java index ae0b662d48..82119926af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java @@ -16,11 +16,28 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import java.util.HashMap; +import java.util.Map; + @Data -public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { +public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { private String relationType; private EntitySearchDirection direction; + + @Override + public TbGetRelatedAttrNodeConfiguration defaultConfiguration() { + TbGetRelatedAttrNodeConfiguration configuration = new TbGetRelatedAttrNodeConfiguration(); + Map attrMapping = new HashMap<>(); + attrMapping.putIfAbsent("temperature", "tempo"); + configuration.setAttrMapping(attrMapping); + configuration.setTelemetry(true); + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); + configuration.setDirection(EntitySearchDirection.FROM); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 26b756176c..22c0b9f0a8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @RuleNode( type = ComponentType.ENRICHMENT, name="related attributes", + configClazz = TbGetRelatedAttrNodeConfiguration.class, nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index 7d9c50bfc6..b5f5e02dbe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @RuleNode( type = ComponentType.ENRICHMENT, name="tenant attributes", + configClazz = TbGetEntityAttrNodeConfiguration.class, nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index d237df83ce..40a647a822 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -36,6 +36,7 @@ import java.util.HashSet; @RuleNode( type = ComponentType.TRANSFORMATION, name="change originator", + configClazz = TbChangeOriginatorNodeConfiguration.class, nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", 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. ") diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java index cf036810c4..3370408231 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java @@ -16,12 +16,24 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @Data -public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration{ +public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { private String originatorSource; private EntitySearchDirection direction; private String relationType; + + @Override + public TbChangeOriginatorNodeConfiguration defaultConfiguration() { + TbChangeOriginatorNodeConfiguration configuration = new TbChangeOriginatorNodeConfiguration(); + configuration.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); + configuration.setDirection(EntitySearchDirection.FROM); + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); + configuration.setStartNewChain(false); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index babdbc3085..626790fcf8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -27,6 +27,7 @@ import javax.script.Bindings; @RuleNode( type = ComponentType.TRANSFORMATION, name = "script", + configClazz = TbTransformMsgNodeConfiguration.class, nodeDescription = "Change Message payload and Metadata using JavaScript", nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.
" + "meta - is a Message metadata.
" + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java index 9cc926b54d..4f9e9eb681 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java @@ -16,9 +16,18 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; @Data -public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration { +public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { private String jsScript; + + @Override + public TbTransformMsgNodeConfiguration defaultConfiguration() { + TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); + configuration.setStartNewChain(false); + configuration.setJsScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' "); + return configuration; + } } diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index f17553545e..ebc48fad61 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -153,16 +153,21 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService) return deferred.promise; } - function getRuleNodeSupportedLinks(nodeType) { //eslint-disable-line - //TODO: - var deferred = $q.defer(); - var linkLabels = [ - { name: 'Success', custom: false }, - { name: 'Fail', custom: false }, - { name: 'Custom', custom: true }, - ]; - deferred.resolve(linkLabels); - return deferred.promise; + function getRuleNodeSupportedLinks(component) { + var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes; + var customRelations = component.configurationDescriptor.nodeDefinition.customRelations; + var linkLabels = []; + for (var i=0;i +
+
+ + + +
+
+
+
+
+
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index e90334bf52..d397d142c8 100644 --- a/ui/src/app/layout/index.js +++ b/ui/src/app/layout/index.js @@ -29,6 +29,7 @@ import thingsboardNoAnimate from '../components/no-animate.directive'; import thingsboardOnFinishRender from '../components/finish-render.directive'; import thingsboardSideMenu from '../components/side-menu.directive'; import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; +import thingsboardJsonObjectEdit from '../components/json-object-edit.directive'; import thingsboardUserMenu from './user-menu.directive'; @@ -90,7 +91,8 @@ export default angular.module('thingsboard.home', [ thingsboardNoAnimate, thingsboardOnFinishRender, thingsboardSideMenu, - thingsboardDashboardAutocomplete + thingsboardDashboardAutocomplete, + thingsboardJsonObjectEdit ]) .config(HomeRoutes) .controller('HomeController', HomeController) diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 360c8282ac..cca2a11648 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1179,6 +1179,7 @@ export default angular.module('thingsboard.locale', []) "delete": "Delete rule node", "rulenode-details": "Rule node details", "debug-mode": "Debug mode", + "configuration": "Configuration", "link-details": "Rule node link details", "add-link": "Add link", "link-label": "Link label", diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index d9bbf2fd8d..b792f138f8 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -151,6 +151,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }, 'mouseLeave': function () { destroyTooltips(); + }, + 'mouseDown': function () { + destroyTooltips(); } } }; @@ -226,16 +229,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, edgeDoubleClick: function (event, edge) { var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); if (sourceNode.component.type != types.ruleNodeType.INPUT.value) { - ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( - (labels) => { - vm.isEditingRuleNode = false; - vm.editingRuleNode = null; - vm.editingRuleNodeLinkLabels = labels; - vm.isEditingRuleNodeLink = true; - vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); - vm.editingRuleNodeLink = angular.copy(edge); - } - ); + vm.isEditingRuleNode = false; + vm.editingRuleNode = null; + vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + vm.isEditingRuleNodeLink = true; + vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); + vm.editingRuleNodeLink = angular.copy(edge); } }, nodeCallbacks: { @@ -267,16 +266,10 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, deferred.resolve(edge); } } else { - ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( - (labels) => { - addRuleNodeLink(event, edge, labels).then( - (link) => { - deferred.resolve(link); - }, - () => { - deferred.reject(); - } - ); + var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + addRuleNodeLink(event, edge, labels).then( + (link) => { + deferred.resolve(link); }, () => { deferred.reject(); @@ -309,24 +302,19 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, y: 10+50*model.nodes.length, connectors: [] }; - if (componentType == types.ruleNodeType.RULE_CHAIN.value) { - node.connectors.push( - { - type: flowchartConstants.leftConnectorType, - id: model.nodes.length - } - ); - } else { + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) { node.connectors.push( { type: flowchartConstants.leftConnectorType, - id: model.nodes.length*2 + id: model.nodes.length * 2 } ); + } + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) { node.connectors.push( { type: flowchartConstants.rightConnectorType, - id: model.nodes.length*2+1 + id: model.nodes.length * 2 + 1 } ); } @@ -398,17 +386,24 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, name: ruleNode.name, nodeClass: vm.types.ruleNodeType[component.type].nodeClass, icon: vm.types.ruleNodeType[component.type].icon, - connectors: [ + connectors: [] + }; + if (component.configurationDescriptor.nodeDefinition.inEnabled) { + node.connectors.push( { type: flowchartConstants.leftConnectorType, id: vm.nextConnectorID++ - }, + } + ); + } + if (component.configurationDescriptor.nodeDefinition.outEnabled) { + node.connectors.push( { type: flowchartConstants.rightConnectorType, id: vm.nextConnectorID++ } - ] - }; + ); + } nodes.push(node); vm.ruleChainModel.nodes.push(node); } @@ -590,6 +585,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } function addRuleNode($event, ruleNode) { + + ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); + $mdDialog.show({ controller: 'AddRuleNodeController', controllerAs: 'vm', @@ -601,13 +599,15 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }).then(function (ruleNode) { ruleNode.id = vm.nextNodeID++; ruleNode.connectors = []; - ruleNode.connectors.push( - { - id: vm.nextConnectorID++, - type: flowchartConstants.leftConnectorType - } - ); - if (ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value) { + if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) { + ruleNode.connectors.push( + { + id: vm.nextConnectorID++, + type: flowchartConstants.leftConnectorType + } + ); + } + if (ruleNode.component.configurationDescriptor.nodeDefinition.outEnabled) { ruleNode.connectors.push( { id: vm.nextConnectorID++, diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html index 0d16e45405..30cf0752cd 100644 --- a/ui/src/app/rulechain/rulenode-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html @@ -38,6 +38,11 @@ ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }} + + diff --git a/ui/src/app/rulechain/rulenode.directive.js b/ui/src/app/rulechain/rulenode.directive.js index 998e9981c6..be3e9c36b5 100644 --- a/ui/src/app/rulechain/rulenode.directive.js +++ b/ui/src/app/rulechain/rulenode.directive.js @@ -14,6 +14,8 @@ * limitations under the License. */ +import './rulenode.scss'; + /* eslint-disable import/no-unresolved, import/default */ import ruleNodeFieldsetTemplate from './rulenode-fieldset.tpl.html'; diff --git a/ui/src/app/rulechain/rulenode.scss b/ui/src/app/rulechain/rulenode.scss new file mode 100644 index 0000000000..febc637a37 --- /dev/null +++ b/ui/src/app/rulechain/rulenode.scss @@ -0,0 +1,22 @@ +/** + * 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. + */ + +.tb-rulenode { + tb-json-object-edit.tb-rule-node-configuration-json { + height: 300px; + display: block; + } +} \ No newline at end of file diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html index 5a521a8333..ffc8a0f20d 100644 --- a/ui/src/app/rulechain/rulenode.tpl.html +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -19,7 +19,7 @@ id="{{node.id}}" ng-attr-style="position: absolute; top: {{ node.y }}px; left: {{ node.x }}px;" ng-dblclick="callbacks.doubleClick($event, node)" - ng-mouseover="callbacks.mouseOver($event, node)" + ng-mousedown="callbacks.mouseDown($event, node)" ng-mouseenter="callbacks.mouseEnter($event, node)" ng-mouseleave="callbacks.mouseLeave($event, node)">
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index 93ff3205c0..6aa662c58c 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -203,6 +203,12 @@ md-sidenav { * THINGSBOARD SPECIFIC ***********************/ +$swift-ease-out-duration: 0.4s !default; +$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; + +$input-label-float-offset: 6px !default; +$input-label-float-scale: 0.75 !default; + label { &.tb-title { pointer-events: none; @@ -213,6 +219,18 @@ label { &.no-padding { padding-bottom: 0px; } + &.tb-required:after { + content: ' *'; + font-size: 13px; + vertical-align: top; + color: rgba(0,0,0,0.54); + } + &.tb-error { + color: rgb(221,44,0); + &.tb-required:after { + color: rgb(221,44,0); + } + } } } From 1776aadbec41b1ad93dacf5eb08fbd58ea92f9af Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 23 Mar 2018 13:32:08 +0200 Subject: [PATCH 28/75] cassandra rate limit clear latest telemetry cleare rule-plugin cache --- .../server/ThingsboardServerApplication.java | 2 + .../plugin/PluginActorMessageProcessor.java | 19 ++- .../rule/RuleActorMessageProcessor.java | 41 +++-- .../src/main/resources/thingsboard.yml | 8 +- .../server/dao/asset/CassandraAssetDao.java | 2 +- .../CassandraBaseAttributesDao.java | 4 +- .../dao/audit/CassandraAuditLogDao.java | 6 +- .../CassandraBaseComponentDescriptorDao.java | 4 +- .../server/dao/device/CassandraDeviceDao.java | 2 +- .../dao/nosql/CassandraAbstractDao.java | 17 +- .../dao/nosql/CassandraAbstractModelDao.java | 10 +- .../dao/nosql/RateLimitedResultSetFuture.java | 148 ++++++++++++++++ .../server/dao/relation/BaseRelationDao.java | 16 +- .../CassandraBaseTimeseriesDao.java | 55 +++--- .../server/dao/util/AsyncRateLimiter.java | 25 +++ .../server/dao/util/BufferedRateLimiter.java | 160 ++++++++++++++++++ .../nosql/RateLimitedResultSetFutureTest.java | 156 +++++++++++++++++ .../dao/util/BufferedRateLimiterTest.java | 134 +++++++++++++++ 18 files changed, 737 insertions(+), 72 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/AsyncRateLimiter.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java index c3444d43f7..751bde6303 100644 --- a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java +++ b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java @@ -19,6 +19,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.Arrays; @@ -26,6 +27,7 @@ import java.util.Arrays; @SpringBootConfiguration @EnableAsync @EnableSwagger2 +@EnableScheduling @ComponentScan({"org.thingsboard.server"}) public class ThingsboardServerApplication { diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java index 6e78e20521..7dfe9a5d81 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java @@ -28,9 +28,14 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; +import org.thingsboard.server.common.msg.session.MsgType; import org.thingsboard.server.extensions.api.plugins.Plugin; import org.thingsboard.server.extensions.api.plugins.PluginInitializationException; import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; @@ -98,7 +103,19 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException { if (state == ComponentLifecycleState.ACTIVE) { - pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); + try { + pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); + } catch (Exception ex) { + RuleToPluginMsg ruleMsg = msg.getMsg(); + MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR; + Integer requestId = 0; + if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) { + requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId(); + } + trustedCtx.reply( + new ResponsePluginToRuleMsg(ruleMsg.getUid(), tenantId, msg.getRuleId(), + BasicStatusCodeResponse.onError(responceMsgType, requestId, ex))); + } } else { //TODO: reply with plugin suspended message } diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java index 2ebebfca33..fd84fe978f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java @@ -15,8 +15,9 @@ */ package org.thingsboard.server.actors.rule; -import java.util.*; - +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.event.LoggingAdapter; import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.util.StringUtils; import org.thingsboard.server.actors.ActorSystemContext; @@ -29,23 +30,17 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.rule.RuleMetaData; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.core.BasicRequest; import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; import org.thingsboard.server.common.msg.core.RuleEngineError; import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; -import org.thingsboard.server.common.msg.session.MsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException; -import org.thingsboard.server.extensions.api.rules.*; import org.thingsboard.server.extensions.api.plugins.PluginAction; import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.*; -import com.fasterxml.jackson.databind.JsonNode; - -import akka.actor.ActorContext; -import akka.actor.ActorRef; -import akka.event.LoggingAdapter; +import java.util.*; class RuleActorMessageProcessor extends ComponentMsgProcessor { @@ -190,18 +185,32 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor { RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid()); if (pendingMsg != null) { ChainProcessingContext ctx = pendingMsg.getCtx(); - Optional ruleResponseOptional = action.convert(msg); - if (ruleResponseOptional.isPresent()) { - ctx.mergeResponse(ruleResponseOptional.get()); - pushToNextRule(context, ctx, null); - } else { + if (isErrorResponce(msg)) { pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS); + } else { + Optional ruleResponseOptional = action.convert(msg); + if (ruleResponseOptional.isPresent()) { + ctx.mergeResponse(ruleResponseOptional.get()); + pushToNextRule(context, ctx, null); + } else { + pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS); + } } } else { logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid()); } } + private boolean isErrorResponce(PluginToRuleMsg msg) { + if (msg instanceof ResponsePluginToRuleMsg) { + if (((ResponsePluginToRuleMsg) msg).getPayload() instanceof BasicStatusCodeResponse) { + BasicStatusCodeResponse responce = (BasicStatusCodeResponse) ((ResponsePluginToRuleMsg) msg).getPayload(); + return !responce.isSuccess(); + } + } + return false; + } + void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) { RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId()); if (pendingMsg != null) { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 77f06e92d3..07face1059 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -133,7 +133,7 @@ quota: intervalMin: 2 database: - type: "${DATABASE_TYPE:sql}" # cassandra OR sql + type: "${DATABASE_TYPE:cassandra}" # cassandra OR sql # Cassandra driver configuration parameters cassandra: @@ -181,6 +181,10 @@ cassandra: default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}" # Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" + buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}" + concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}" + permit_max_wait_time: "${PERMIT_MAX_WAIT_TIME:120000}" + rate_limit_print_interval_ms: "${CASSANDRA_QUERY_RATE_LIMIT_PRINT_MS:30000}" # SQL configuration parameters sql: @@ -222,7 +226,7 @@ caffeine: specs: relations: timeToLiveInMinutes: 1440 - maxSize: 100000 + maxSize: 0 deviceCredentials: timeToLiveInMinutes: 1440 maxSize: 100000 diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java index 4f923fe808..64ec718fdd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java @@ -148,7 +148,7 @@ public class CassandraAssetDao extends CassandraAbstractSearchTextDao>() { @Nullable @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java index 932d6b9147..8ae9dc8ad4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java @@ -147,12 +147,12 @@ public class CassandraBaseAttributesDao extends CassandraAbstractAsyncDao implem .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)) .and(eq(ATTRIBUTE_KEY_COLUMN, key)); log.debug("Remove request: {}", delete.toString()); - return getFuture(getSession().executeAsync(delete), rs -> null); + return getFuture(executeAsyncWrite(delete), rs -> null); } private PreparedStatement getSaveStmt() { if (saveStmt == null) { - saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.ATTRIBUTES_KV_CF + + saveStmt = prepare("INSERT INTO " + ModelConstants.ATTRIBUTES_KV_CF + "(" + ENTITY_TYPE_COLUMN + "," + ENTITY_ID_COLUMN + "," + ATTRIBUTE_TYPE_COLUMN + diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java index 27f7adc669..fd02b5f880 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java @@ -244,12 +244,12 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao>() { @Nullable @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java index 94299caeb4..5c93066d90 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.model.type.*; +import org.thingsboard.server.dao.util.BufferedRateLimiter; @Slf4j public abstract class CassandraAbstractDao { @@ -28,12 +29,15 @@ public abstract class CassandraAbstractDao { @Autowired protected CassandraCluster cluster; + @Autowired + private BufferedRateLimiter rateLimiter; + private Session session; private ConsistencyLevel defaultReadLevel; private ConsistencyLevel defaultWriteLevel; - protected Session getSession() { + private Session getSession() { if (session == null) { session = cluster.getSession(); defaultReadLevel = cluster.getDefaultReadConsistencyLevel(); @@ -50,6 +54,10 @@ public abstract class CassandraAbstractDao { return session; } + protected PreparedStatement prepare(String query) { + return getSession().prepare(query); + } + private void registerCodecIfNotFound(CodecRegistry registry, TypeCodec codec) { try { registry.codecFor(codec.getCqlType(), codec.getJavaType()); @@ -76,10 +84,7 @@ public abstract class CassandraAbstractDao { private ResultSet execute(Statement statement, ConsistencyLevel level) { log.debug("Execute cassandra statement {}", statement); - if (statement.getConsistencyLevel() == null) { - statement.setConsistencyLevel(level); - } - return getSession().execute(statement); + return executeAsync(statement, level).getUninterruptibly(); } private ResultSetFuture executeAsync(Statement statement, ConsistencyLevel level) { @@ -87,6 +92,6 @@ public abstract class CassandraAbstractDao { if (statement.getConsistencyLevel() == null) { statement.setConsistencyLevel(level); } - return getSession().executeAsync(statement); + return new RateLimitedResultSetFuture(getSession(), rateLimiter, statement); } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java index bad7b9e9b9..000316b4ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java @@ -60,7 +60,7 @@ public abstract class CassandraAbstractModelDao, D> exte List list = Collections.emptyList(); if (statement != null) { statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSet resultSet = getSession().execute(statement); + ResultSet resultSet = executeRead(statement); Result result = getMapper().map(resultSet); if (result != null) { list = result.all(); @@ -72,7 +72,7 @@ public abstract class CassandraAbstractModelDao, D> exte protected ListenableFuture> findListByStatementAsync(Statement statement) { if (statement != null) { statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSetFuture resultSetFuture = getSession().executeAsync(statement); + ResultSetFuture resultSetFuture = executeAsyncRead(statement); return Futures.transform(resultSetFuture, new Function>() { @Nullable @Override @@ -94,7 +94,7 @@ public abstract class CassandraAbstractModelDao, D> exte E object = null; if (statement != null) { statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSet resultSet = getSession().execute(statement); + ResultSet resultSet = executeRead(statement); Result result = getMapper().map(resultSet); if (result != null) { object = result.one(); @@ -106,7 +106,7 @@ public abstract class CassandraAbstractModelDao, D> exte protected ListenableFuture findOneByStatementAsync(Statement statement) { if (statement != null) { statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSetFuture resultSetFuture = getSession().executeAsync(statement); + ResultSetFuture resultSetFuture = executeAsyncRead(statement); return Futures.transform(resultSetFuture, new Function() { @Nullable @Override @@ -181,7 +181,7 @@ public abstract class CassandraAbstractModelDao, D> exte public boolean removeById(UUID key) { Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key)); log.debug("Remove request: {}", delete.toString()); - return getSession().execute(delete).wasApplied(); + return executeWrite(delete).wasApplied(); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java new file mode 100644 index 0000000000..2674c6ddea --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java @@ -0,0 +1,148 @@ +/** + * 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.nosql; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.ResultSetFuture; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.Statement; +import com.google.common.base.Function; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.Uninterruptibles; +import org.thingsboard.server.dao.util.AsyncRateLimiter; + +import javax.annotation.Nullable; +import java.util.concurrent.*; + +public class RateLimitedResultSetFuture implements ResultSetFuture { + + private final ListenableFuture originalFuture; + private final ListenableFuture rateLimitFuture; + + public RateLimitedResultSetFuture(Session session, AsyncRateLimiter rateLimiter, Statement statement) { + this.rateLimitFuture = rateLimiter.acquireAsync(); + this.originalFuture = Futures.transform(rateLimitFuture, + (Function) i -> executeAsyncWithRelease(rateLimiter, session, statement)); + } + + @Override + public ResultSet getUninterruptibly() { + return safeGet().getUninterruptibly(); + } + + @Override + public ResultSet getUninterruptibly(long timeout, TimeUnit unit) throws TimeoutException { + long rateLimitStart = System.nanoTime(); + ResultSetFuture resultSetFuture = null; + try { + resultSetFuture = originalFuture.get(timeout, unit); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + long rateLimitDurationNano = System.nanoTime() - rateLimitStart; + long innerTimeoutNano = unit.toNanos(timeout) - rateLimitDurationNano; + if (innerTimeoutNano > 0) { + return resultSetFuture.getUninterruptibly(innerTimeoutNano, TimeUnit.NANOSECONDS); + } + throw new TimeoutException("Timeout waiting for task."); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + if (originalFuture.isDone()) { + return safeGet().cancel(mayInterruptIfRunning); + } else { + return originalFuture.cancel(mayInterruptIfRunning); + } + } + + @Override + public boolean isCancelled() { + if (originalFuture.isDone()) { + return safeGet().isCancelled(); + } + + return originalFuture.isCancelled(); + } + + @Override + public boolean isDone() { + return originalFuture.isDone() && safeGet().isDone(); + } + + @Override + public ResultSet get() throws InterruptedException, ExecutionException { + return safeGet().get(); + } + + @Override + public ResultSet get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + long rateLimitStart = System.nanoTime(); + ResultSetFuture resultSetFuture = originalFuture.get(timeout, unit); + long rateLimitDurationNano = System.nanoTime() - rateLimitStart; + long innerTimeoutNano = unit.toNanos(timeout) - rateLimitDurationNano; + if (innerTimeoutNano > 0) { + return resultSetFuture.get(innerTimeoutNano, TimeUnit.NANOSECONDS); + } + throw new TimeoutException("Timeout waiting for task."); + } + + @Override + public void addListener(Runnable listener, Executor executor) { + originalFuture.addListener(() -> { + try { + ResultSetFuture resultSetFuture = Uninterruptibles.getUninterruptibly(originalFuture); + resultSetFuture.addListener(listener, executor); + } catch (CancellationException e) { + cancel(false); + return; + } catch (ExecutionException e) { + Futures.immediateFailedFuture(e).addListener(listener, executor); + } + }, executor); + } + + private ResultSetFuture safeGet() { + try { + return originalFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } + + private ResultSetFuture executeAsyncWithRelease(AsyncRateLimiter rateLimiter, Session session, Statement statement) { + try { + ResultSetFuture resultSetFuture = session.executeAsync(statement); + Futures.addCallback(resultSetFuture, new FutureCallback() { + @Override + public void onSuccess(@Nullable ResultSet result) { + rateLimiter.release(); + } + + @Override + public void onFailure(Throwable t) { + rateLimiter.release(); + } + }); + return resultSetFuture; + } catch (RuntimeException re) { + rateLimiter.release(); + throw re; + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java index 9e252412b2..55838d6462 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java @@ -242,7 +242,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getSaveStmt() { if (saveStmt == null) { - saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + + saveStmt = prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + "(" + ModelConstants.RELATION_FROM_ID_PROPERTY + "," + ModelConstants.RELATION_FROM_TYPE_PROPERTY + "," + ModelConstants.RELATION_TO_ID_PROPERTY + @@ -257,7 +257,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getDeleteStmt() { if (deleteStmt == null) { - deleteStmt = getSession().prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + + deleteStmt = prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?" + AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ?" + @@ -270,7 +270,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getDeleteAllByEntityStmt() { if (deleteAllByEntityStmt == null) { - deleteAllByEntityStmt = getSession().prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + + deleteAllByEntityStmt = prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?"); } @@ -279,7 +279,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getFindAllByFromStmt() { if (findAllByFromStmt == null) { - findAllByFromStmt = getSession().prepare(SELECT_COLUMNS + " " + + findAllByFromStmt = prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + EQUAL_TO_PARAM + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + EQUAL_TO_PARAM + @@ -290,7 +290,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getFindAllByFromAndTypeStmt() { if (findAllByFromAndTypeStmt == null) { - findAllByFromAndTypeStmt = getSession().prepare(SELECT_COLUMNS + " " + + findAllByFromAndTypeStmt = prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + EQUAL_TO_PARAM + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + EQUAL_TO_PARAM + @@ -303,7 +303,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getFindAllByToStmt() { if (findAllByToStmt == null) { - findAllByToStmt = getSession().prepare(SELECT_COLUMNS + " " + + findAllByToStmt = prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " + WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + EQUAL_TO_PARAM + AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + EQUAL_TO_PARAM + @@ -314,7 +314,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getFindAllByToAndTypeStmt() { if (findAllByToAndTypeStmt == null) { - findAllByToAndTypeStmt = getSession().prepare(SELECT_COLUMNS + " " + + findAllByToAndTypeStmt = prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " + WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + EQUAL_TO_PARAM + AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + EQUAL_TO_PARAM + @@ -327,7 +327,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati private PreparedStatement getCheckRelationStmt() { if (checkRelationStmt == null) { - checkRelationStmt = getSession().prepare(SELECT_COLUMNS + " " + + checkRelationStmt = prepare(SELECT_COLUMNS + " " + FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + EQUAL_TO_PARAM + AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + EQUAL_TO_PARAM + diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index d620e11f1d..aba3eefbc9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -73,7 +73,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private PreparedStatement partitionInsertStmt; private PreparedStatement partitionInsertTtlStmt; - private PreparedStatement[] latestInsertStmts; + private PreparedStatement latestInsertStmt; private PreparedStatement[] saveStmts; private PreparedStatement[] saveTtlStmts; private PreparedStatement[] fetchStmts; @@ -306,13 +306,15 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem @Override public ListenableFuture saveLatest(EntityId entityId, TsKvEntry tsKvEntry) { - DataType type = tsKvEntry.getDataType(); - BoundStatement stmt = getLatestStmt(type).bind() + BoundStatement stmt = getLatestStmt().bind() .setString(0, entityId.getEntityType().name()) .setUUID(1, entityId.getId()) .setString(2, tsKvEntry.getKey()) - .setLong(3, tsKvEntry.getTs()); - addValue(tsKvEntry, stmt, 4); + .setLong(3, tsKvEntry.getTs()) + .set(4, tsKvEntry.getBooleanValue().orElse(null), Boolean.class) + .set(5, tsKvEntry.getStrValue().orElse(null), String.class) + .set(6, tsKvEntry.getLongValue().orElse(null), Long.class) + .set(7, tsKvEntry.getDoubleValue().orElse(null), Double.class); return getFuture(executeAsyncWrite(stmt), rs -> null); } @@ -381,7 +383,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem if (saveStmts == null) { saveStmts = new PreparedStatement[DataType.values().length]; for (DataType type : DataType.values()) { - saveStmts[type.ordinal()] = getSession().prepare(INSERT_INTO + ModelConstants.TS_KV_CF + + saveStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + "," + ModelConstants.KEY_COLUMN + @@ -398,7 +400,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem if (saveTtlStmts == null) { saveTtlStmts = new PreparedStatement[DataType.values().length]; for (DataType type : DataType.values()) { - saveTtlStmts[type.ordinal()] = getSession().prepare(INSERT_INTO + ModelConstants.TS_KV_CF + + saveTtlStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + "," + ModelConstants.KEY_COLUMN + @@ -420,7 +422,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem } else if (type == Aggregation.AVG && fetchStmts[Aggregation.SUM.ordinal()] != null) { fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()]; } else { - fetchStmts[type.ordinal()] = getSession().prepare(SELECT_PREFIX + + fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX + String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF + " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM @@ -435,26 +437,29 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem return fetchStmts[aggType.ordinal()]; } - private PreparedStatement getLatestStmt(DataType dataType) { - if (latestInsertStmts == null) { - latestInsertStmts = new PreparedStatement[DataType.values().length]; - for (DataType type : DataType.values()) { - latestInsertStmts[type.ordinal()] = getSession().prepare(INSERT_INTO + ModelConstants.TS_KV_LATEST_CF + - "(" + ModelConstants.ENTITY_TYPE_COLUMN + - "," + ModelConstants.ENTITY_ID_COLUMN + - "," + ModelConstants.KEY_COLUMN + - "," + ModelConstants.TS_COLUMN + - "," + getColumnName(type) + ")" + - " VALUES(?, ?, ?, ?, ?)"); - } + private PreparedStatement getLatestStmt() { + if (latestInsertStmt == null) { +// latestInsertStmt = new PreparedStatement[DataType.values().length]; +// for (DataType type : DataType.values()) { + latestInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_LATEST_CF + + "(" + ModelConstants.ENTITY_TYPE_COLUMN + + "," + ModelConstants.ENTITY_ID_COLUMN + + "," + ModelConstants.KEY_COLUMN + + "," + ModelConstants.TS_COLUMN + + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + + "," + ModelConstants.STRING_VALUE_COLUMN + + "," + ModelConstants.LONG_VALUE_COLUMN + + "," + ModelConstants.DOUBLE_VALUE_COLUMN + ")" + + " VALUES(?, ?, ?, ?, ?)"); +// } } - return latestInsertStmts[dataType.ordinal()]; + return latestInsertStmt; } private PreparedStatement getPartitionInsertStmt() { if (partitionInsertStmt == null) { - partitionInsertStmt = getSession().prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF + + partitionInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + "," + ModelConstants.PARTITION_COLUMN + @@ -466,7 +471,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private PreparedStatement getPartitionInsertTtlStmt() { if (partitionInsertTtlStmt == null) { - partitionInsertTtlStmt = getSession().prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF + + partitionInsertTtlStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + "," + ModelConstants.PARTITION_COLUMN + @@ -479,7 +484,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private PreparedStatement getFindLatestStmt() { if (findLatestStmt == null) { - findLatestStmt = getSession().prepare(SELECT_PREFIX + + findLatestStmt = prepare(SELECT_PREFIX + ModelConstants.KEY_COLUMN + "," + ModelConstants.TS_COLUMN + "," + ModelConstants.STRING_VALUE_COLUMN + "," + @@ -496,7 +501,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private PreparedStatement getFindAllLatestStmt() { if (findAllLatestStmt == null) { - findAllLatestStmt = getSession().prepare(SELECT_PREFIX + + findAllLatestStmt = prepare(SELECT_PREFIX + ModelConstants.KEY_COLUMN + "," + ModelConstants.TS_COLUMN + "," + ModelConstants.STRING_VALUE_COLUMN + "," + diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AsyncRateLimiter.java b/dao/src/main/java/org/thingsboard/server/dao/util/AsyncRateLimiter.java new file mode 100644 index 0000000000..6fb21d6adb --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/util/AsyncRateLimiter.java @@ -0,0 +1,25 @@ +/** + * 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.util; + +import com.google.common.util.concurrent.ListenableFuture; + +public interface AsyncRateLimiter { + + ListenableFuture acquireAsync(); + + void release(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java new file mode 100644 index 0000000000..de07dbfa47 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java @@ -0,0 +1,160 @@ +/** + * 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.util; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +@Component +@Slf4j +public class BufferedRateLimiter implements AsyncRateLimiter { + + private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); + + private final int permitsLimit; + private final int maxPermitWaitTime; + private final AtomicInteger permits; + private final BlockingQueue queue; + + private final AtomicInteger maxQueueSize = new AtomicInteger(); + private final AtomicInteger maxGrantedPermissions = new AtomicInteger(); + + public BufferedRateLimiter(@Value("${cassandra.query.buffer_size}") int queueLimit, + @Value("${cassandra.query.concurrent_limit}") int permitsLimit, + @Value("${cassandra.query.permit_max_wait_time}") int maxPermitWaitTime) { + this.permitsLimit = permitsLimit; + this.maxPermitWaitTime = maxPermitWaitTime; + this.permits = new AtomicInteger(); + this.queue = new LinkedBlockingQueue<>(queueLimit); + } + + @Override + public ListenableFuture acquireAsync() { + if (queue.isEmpty()) { + if (permits.incrementAndGet() <= permitsLimit) { + if (permits.get() > maxGrantedPermissions.get()) { + maxGrantedPermissions.set(permits.get()); + } + return Futures.immediateFuture(null); + } + permits.decrementAndGet(); + } + + return putInQueue(); + } + + @Override + public void release() { + permits.decrementAndGet(); + reprocessQueue(); + } + + private void reprocessQueue() { + while (permits.get() < permitsLimit) { + if (permits.incrementAndGet() <= permitsLimit) { + if (permits.get() > maxGrantedPermissions.get()) { + maxGrantedPermissions.set(permits.get()); + } + LockedFuture lockedFuture = queue.poll(); + if (lockedFuture != null) { + lockedFuture.latch.countDown(); + } else { + permits.decrementAndGet(); + break; + } + } else { + permits.decrementAndGet(); + } + } + } + + private LockedFuture createLockedFuture() { + CountDownLatch latch = new CountDownLatch(1); + ListenableFuture future = pool.submit(() -> { + latch.await(); + return null; + }); + return new LockedFuture(latch, future, System.currentTimeMillis()); + } + + private ListenableFuture putInQueue() { + + int size = queue.size(); + if (size > maxQueueSize.get()) { + maxQueueSize.set(size); + } + + if (queue.remainingCapacity() > 0) { + try { + LockedFuture lockedFuture = createLockedFuture(); + if (!queue.offer(lockedFuture, 1, TimeUnit.SECONDS)) { + lockedFuture.cancelFuture(); + return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject")); + } + return lockedFuture.future; + } catch (InterruptedException e) { + return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Task interrupted. Reject")); + } + } + return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject")); + } + + @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") + public void printStats() { + int expiredCount = 0; + for (LockedFuture lockedFuture : queue) { + if (lockedFuture.isExpired()) { + lockedFuture.cancelFuture(); + expiredCount++; + } + } + log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}]", maxQueueSize.getAndSet(0), + maxGrantedPermissions.getAndSet(0), expiredCount); + } + + private class LockedFuture { + final CountDownLatch latch; + final ListenableFuture future; + final long createTime; + + public LockedFuture(CountDownLatch latch, ListenableFuture future, long createTime) { + this.latch = latch; + this.future = future; + this.createTime = createTime; + } + + void cancelFuture() { + future.cancel(false); + latch.countDown(); + } + + boolean isExpired() { + return (System.currentTimeMillis() - createTime) > maxPermitWaitTime; + } + + } + + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java b/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java new file mode 100644 index 0000000000..fa62c2b9b0 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java @@ -0,0 +1,156 @@ +/** + * 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.nosql; + +import com.datastax.driver.core.*; +import com.datastax.driver.core.exceptions.UnsupportedFeatureException; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.thingsboard.server.dao.util.AsyncRateLimiter; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class RateLimitedResultSetFutureTest { + + private RateLimitedResultSetFuture resultSetFuture; + + @Mock + private AsyncRateLimiter rateLimiter; + @Mock + private Session session; + @Mock + private Statement statement; + @Mock + private ResultSetFuture realFuture; + @Mock + private ResultSet rows; + @Mock + private Row row; + + @Test + public void doNotReleasePermissionIfRateLimitFutureFailed() throws InterruptedException { + when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFailedFuture(new IllegalArgumentException())); + resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); + Thread.sleep(1000L); + verify(rateLimiter).acquireAsync(); + try { + assertTrue(resultSetFuture.isDone()); + fail(); + } catch (Exception e) { + assertTrue(e instanceof IllegalStateException); + Throwable actualCause = e.getCause(); + assertTrue(actualCause instanceof ExecutionException); + } + verifyNoMoreInteractions(session, rateLimiter, statement); + + } + + @Test + public void getUninterruptiblyDelegateToCassandra() throws InterruptedException, ExecutionException { + when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFuture(null)); + when(session.executeAsync(statement)).thenReturn(realFuture); + Mockito.doAnswer((Answer) invocation -> { + Object[] args = invocation.getArguments(); + Runnable task = (Runnable) args[0]; + task.run(); + return null; + }).when(realFuture).addListener(Mockito.any(), Mockito.any()); + + when(realFuture.getUninterruptibly()).thenReturn(rows); + + resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); + ResultSet actual = resultSetFuture.getUninterruptibly(); + assertSame(rows, actual); + verify(rateLimiter, times(1)).acquireAsync(); + verify(rateLimiter, times(1)).release(); + } + + @Test + public void addListenerAllowsFutureTransformation() throws InterruptedException, ExecutionException { + when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFuture(null)); + when(session.executeAsync(statement)).thenReturn(realFuture); + Mockito.doAnswer((Answer) invocation -> { + Object[] args = invocation.getArguments(); + Runnable task = (Runnable) args[0]; + task.run(); + return null; + }).when(realFuture).addListener(Mockito.any(), Mockito.any()); + + when(realFuture.get()).thenReturn(rows); + when(rows.one()).thenReturn(row); + + resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); + + ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one); + Row actualRow = transform.get(); + + assertSame(row, actualRow); + verify(rateLimiter, times(1)).acquireAsync(); + verify(rateLimiter, times(1)).release(); + } + + @Test + public void immidiateCassandraExceptionReturnsPermit() throws InterruptedException, ExecutionException { + when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFuture(null)); + when(session.executeAsync(statement)).thenThrow(new UnsupportedFeatureException(ProtocolVersion.V3, "hjg")); + resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); + ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one); + try { + transform.get(); + fail(); + } catch (Exception e) { + assertTrue(e instanceof ExecutionException); + } + verify(rateLimiter, times(1)).acquireAsync(); + verify(rateLimiter, times(1)).release(); + } + + @Test + public void queryTimeoutReturnsPermit() throws InterruptedException, ExecutionException { + when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFuture(null)); + when(session.executeAsync(statement)).thenReturn(realFuture); + Mockito.doAnswer((Answer) invocation -> { + Object[] args = invocation.getArguments(); + Runnable task = (Runnable) args[0]; + task.run(); + return null; + }).when(realFuture).addListener(Mockito.any(), Mockito.any()); + + when(realFuture.get()).thenThrow(new ExecutionException("Fail", new TimeoutException("timeout"))); + resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); + ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one); + try { + transform.get(); + fail(); + } catch (Exception e) { + assertTrue(e instanceof ExecutionException); + } + verify(rateLimiter, times(1)).acquireAsync(); + verify(rateLimiter, times(1)).release(); + } + +} \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java b/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java new file mode 100644 index 0000000000..5bfc3b6e95 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java @@ -0,0 +1,134 @@ +/** + * 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.util; + +import com.google.common.util.concurrent.*; +import org.junit.Test; + +import javax.annotation.Nullable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + + +public class BufferedRateLimiterTest { + + @Test + public void finishedFutureReturnedIfPermitsAreGranted() { + BufferedRateLimiter limiter = new BufferedRateLimiter(10, 10, 100); + ListenableFuture actual = limiter.acquireAsync(); + assertTrue(actual.isDone()); + } + + @Test + public void notFinishedFutureReturnedIfPermitsAreNotGranted() { + BufferedRateLimiter limiter = new BufferedRateLimiter(10, 1, 100); + ListenableFuture actual1 = limiter.acquireAsync(); + ListenableFuture actual2 = limiter.acquireAsync(); + assertTrue(actual1.isDone()); + assertFalse(actual2.isDone()); + } + + @Test + public void failedFutureReturnedIfQueueIsfull() { + BufferedRateLimiter limiter = new BufferedRateLimiter(1, 1, 100); + ListenableFuture actual1 = limiter.acquireAsync(); + ListenableFuture actual2 = limiter.acquireAsync(); + ListenableFuture actual3 = limiter.acquireAsync(); + + assertTrue(actual1.isDone()); + assertFalse(actual2.isDone()); + assertTrue(actual3.isDone()); + try { + actual3.get(); + fail(); + } catch (Exception e) { + assertTrue(e instanceof ExecutionException); + Throwable actualCause = e.getCause(); + assertTrue(actualCause instanceof IllegalStateException); + assertEquals("Rate Limit Buffer is full. Reject", actualCause.getMessage()); + } + } + + @Test + public void releasedPermitTriggerTasksFromQueue() throws InterruptedException { + BufferedRateLimiter limiter = new BufferedRateLimiter(10, 2, 100); + ListenableFuture actual1 = limiter.acquireAsync(); + ListenableFuture actual2 = limiter.acquireAsync(); + ListenableFuture actual3 = limiter.acquireAsync(); + ListenableFuture actual4 = limiter.acquireAsync(); + assertTrue(actual1.isDone()); + assertTrue(actual2.isDone()); + assertFalse(actual3.isDone()); + assertFalse(actual4.isDone()); + limiter.release(); + TimeUnit.MILLISECONDS.sleep(100L); + assertTrue(actual3.isDone()); + assertFalse(actual4.isDone()); + limiter.release(); + TimeUnit.MILLISECONDS.sleep(100L); + assertTrue(actual4.isDone()); + } + + @Test + public void permitsReleasedInConcurrentMode() throws InterruptedException { + BufferedRateLimiter limiter = new BufferedRateLimiter(10, 2, 100); + AtomicInteger actualReleased = new AtomicInteger(); + AtomicInteger actualRejected = new AtomicInteger(); + ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5)); + for (int i = 0; i < 100; i++) { + ListenableFuture> submit = pool.submit(limiter::acquireAsync); + Futures.addCallback(submit, new FutureCallback>() { + @Override + public void onSuccess(@Nullable ListenableFuture result) { + Futures.addCallback(result, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + limiter.release(); + actualReleased.incrementAndGet(); + } + + @Override + public void onFailure(Throwable t) { + actualRejected.incrementAndGet(); + } + }); + } + + @Override + public void onFailure(Throwable t) { + } + }); + } + + TimeUnit.SECONDS.sleep(2); + assertTrue("Unexpected released count " + actualReleased.get(), + actualReleased.get() > 10 && actualReleased.get() < 20); + assertTrue("Unexpected rejected count " + actualRejected.get(), + actualRejected.get() > 80 && actualRejected.get() < 90); + + } + + +} \ No newline at end of file From f49d480d30ac5b5ac187b6a7676f19c129616d9c Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 23 Mar 2018 13:35:04 +0200 Subject: [PATCH 29/75] No More Plugins --- .../controller/TelemetryController.java | 350 ++++++++++++++++++ .../server/controller/ValidationCallback.java | 73 ++++ .../service/security/model/SecurityUser.java | 1 + .../thingsboard/server/common/data/User.java | 12 + .../api/plugins/PluginConstants.java | 2 + 5 files changed, 438 insertions(+) create mode 100644 application/src/main/java/org/thingsboard/server/controller/TelemetryController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java new file mode 100644 index 0000000000..dcc4a5cf54 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -0,0 +1,350 @@ +package org.thingsboard.server.controller; + +import com.google.common.base.Function; +import com.google.common.util.concurrent.FutureCallback; +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.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.actors.plugin.ValidationResult; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.exception.ThingsboardException; +import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity; +import org.thingsboard.server.extensions.api.plugins.PluginConstants; +import org.thingsboard.server.service.security.model.SecurityUser; + +import javax.annotation.Nullable; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +/** + * Created by ashvayka on 22.03.18. + */ +@RestController +@RequestMapping(PluginConstants.TELEMETRY_URL_PREFIX) +@Slf4j +public class TelemetryController extends BaseController { + + public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!"; + public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; + public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!"; + + @Autowired + private AttributesService attributesService; + + @Autowired + private TimeseriesService tsService; + + private ExecutorService executor; + + public void initExecutor() { + executor = Executors.newSingleThreadExecutor(); + } + + @PreDestroy + public void shutdownExecutor() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/keys/ATTRIBUTES", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeKeys( + @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException { + DeferredResult response = new DeferredResult(); + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + + validate(getCurrentUser(), entityId, new ValidationCallback(response, + new FutureCallback>() { + @Override + public void onSuccess(@Nullable DeferredResult result) { + List>> futures = new ArrayList<>(); + for (String scope : DataConstants.allScopes()) { + futures.add(attributesService.findAll(entityId, scope)); + } + + ListenableFuture> future = Futures.transform(Futures.successfulAsList(futures), + (Function>, ? extends List>) input -> { + List tmp = new ArrayList<>(); + if (input != null) { + input.forEach(tmp::addAll); + } + return tmp; + }, executor); + + Futures.addCallback(future, getAttributeKeysPluginCallback(result)); + } + + @Override + public void onFailure(Throwable t) { + handleError(t, response, HttpStatus.INTERNAL_SERVER_ERROR); + } + })); + + return response; + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/keys/ATTRIBUTES/{scope}", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeKeysByScope() { + return null; + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/values/ATTRIBUTES", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeValues() { + return null; + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/values/ATTRIBUTES", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeValuesByScope() { + return null; + } + + private FutureCallback> getAttributeKeysPluginCallback(final DeferredResult response) { + return new FutureCallback>() { + + @Override + public void onSuccess(List attributes) { + List keys = attributes.stream().map(KvEntry::getKey).collect(Collectors.toList()); + response.setResult(new ResponseEntity<>(keys, HttpStatus.OK)); + } + + @Override + public void onFailure(Throwable e) { + log.error("Failed to fetch attributes", e); + handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR); + } + }; + } + + private void handleError(Throwable e, final DeferredResult response, HttpStatus defaultErrorStatus) { + ResponseEntity responseEntity; + if (e != null && e instanceof ToErrorResponseEntity) { + responseEntity = ((ToErrorResponseEntity) e).toErrorResponseEntity(); + } else if (e != null && e instanceof IllegalArgumentException) { + responseEntity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + } else { + responseEntity = new ResponseEntity<>(defaultErrorStatus); + } + response.setResult(responseEntity); + } + + private void validate(SecurityUser currentUser, EntityId entityId, ValidationCallback callback) { + switch (entityId.getEntityType()) { + case DEVICE: + validateDevice(currentUser, entityId, callback); + return; +// case ASSET: +// validateAsset(ctx, entityId, callback); +// return; +// case RULE: +// validateRule(ctx, entityId, callback); +// return; +// case RULE_CHAIN: +// validateRuleChain(ctx, entityId, callback); +// return; +// case PLUGIN: +// validatePlugin(ctx, entityId, callback); +// return; +// case CUSTOMER: +// validateCustomer(ctx, entityId, callback); +// return; +// case TENANT: +// validateTenant(ctx, entityId, callback); +// return; + default: + //TODO: add support of other entities + throw new IllegalStateException("Not Implemented!"); + } + } + + private void validateDevice(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) { + if (currentUser.isSystemAdmin()) { + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); + } else { + ListenableFuture deviceFuture = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId())); + Futures.addCallback(deviceFuture, getCallback(callback, device -> { + if (device == null) { + return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND); + } else { + if (!device.getTenantId().equals(currentUser.getTenantId())) { + return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!"); + } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) { + return ValidationResult.accessDenied("Device doesn't belong to the current Customer!"); + } else { + return ValidationResult.ok(); + } + } + })); + } + } + + private FutureCallback getCallback(ValidationCallback callback, Function transformer) { + return new FutureCallback() { + @Override + public void onSuccess(@Nullable T result) { + callback.onSuccess(transformer.apply(result)); + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }; + } +// +// private void validateAsset(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isSystemAdmin()) { +// callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture assetFuture = pluginCtx.assetService.findAssetByIdAsync(new AssetId(entityId.getId())); +// Futures.addCallback(assetFuture, getCallback(callback, asset -> { +// if (asset == null) { +// return ValidationResult.entityNotFound("Asset with requested id wasn't found!"); +// } else { +// if (!asset.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!"); +// } else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) { +// return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateRule(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture ruleFuture = pluginCtx.ruleService.findRuleByIdAsync(new RuleId(entityId.getId())); +// Futures.addCallback(ruleFuture, getCallback(callback, rule -> { +// if (rule == null) { +// return ValidationResult.entityNotFound("Rule with requested id wasn't found!"); +// } else { +// if (ctx.isTenantAdmin() && !rule.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Rule doesn't belong to the current Tenant!"); +// } else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) { +// return ValidationResult.accessDenied("Rule is not in system scope!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateRuleChain(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture ruleChainFuture = pluginCtx.ruleChainService.findRuleChainByIdAsync(new RuleChainId(entityId.getId())); +// Futures.addCallback(ruleChainFuture, getCallback(callback, ruleChain -> { +// if (ruleChain == null) { +// return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!"); +// } else { +// if (ctx.isTenantAdmin() && !ruleChain.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!"); +// } else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) { +// return ValidationResult.accessDenied("Rule chain is not in system scope!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// +// private void validatePlugin(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture pluginFuture = pluginCtx.pluginService.findPluginByIdAsync(new PluginId(entityId.getId())); +// Futures.addCallback(pluginFuture, getCallback(callback, plugin -> { +// if (plugin == null) { +// return ValidationResult.entityNotFound("Plugin with requested id wasn't found!"); +// } else { +// if (ctx.isTenantAdmin() && !plugin.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Plugin doesn't belong to the current Tenant!"); +// } else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) { +// return ValidationResult.accessDenied("Plugin is not in system scope!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateCustomer(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isSystemAdmin()) { +// callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture customerFuture = pluginCtx.customerService.findCustomerByIdAsync(new CustomerId(entityId.getId())); +// Futures.addCallback(customerFuture, getCallback(callback, customer -> { +// if (customer == null) { +// return ValidationResult.entityNotFound("Customer with requested id wasn't found!"); +// } else { +// if (!customer.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!"); +// } else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) { +// return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateTenant(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else if (ctx.isSystemAdmin()) { +// callback.onSuccess(this, ValidationResult.ok()); +// } else { +// ListenableFuture tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId())); +// Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { +// if (tenant == null) { +// return ValidationResult.entityNotFound("Tenant with requested id wasn't found!"); +// } else if (!tenant.getId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!"); +// } else { +// return ValidationResult.ok(); +// } +// })); +// } +// } + + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java b/application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java new file mode 100644 index 0000000000..ead90ea2f2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java @@ -0,0 +1,73 @@ +/** + * 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.controller; + +import com.google.common.util.concurrent.FutureCallback; +import org.springframework.http.ResponseEntity; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.actors.plugin.ValidationResult; +import org.thingsboard.server.actors.plugin.ValidationResultCode; +import org.thingsboard.server.extensions.api.exception.AccessDeniedException; +import org.thingsboard.server.extensions.api.exception.EntityNotFoundException; +import org.thingsboard.server.extensions.api.exception.InternalErrorException; +import org.thingsboard.server.extensions.api.exception.UnauthorizedException; + +/** + * Created by ashvayka on 21.02.17. + */ +public class ValidationCallback implements FutureCallback { + + private final DeferredResult response; + private final FutureCallback> action; + + public ValidationCallback(DeferredResult response, FutureCallback> action) { + this.response = response; + this.action = action; + } + + @Override + public void onSuccess(ValidationResult result) { + ValidationResultCode resultCode = result.getResultCode(); + if (resultCode == ValidationResultCode.OK) { + action.onSuccess(response); + } else { + Exception e; + switch (resultCode) { + case ENTITY_NOT_FOUND: + e = new EntityNotFoundException(result.getMessage()); + break; + case UNAUTHORIZED: + e = new UnauthorizedException(result.getMessage()); + break; + case ACCESS_DENIED: + e = new AccessDeniedException(result.getMessage()); + break; + case INTERNAL_ERROR: + e = new InternalErrorException(result.getMessage()); + break; + default: + e = new UnauthorizedException("Permission denied."); + break; + } + onFailure(e); + } + } + + @Override + public void onFailure(Throwable e) { + action.onFailure(e); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java index f4a28a03a6..91ee7bf6d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.model; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; import java.util.Collection; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index 3b957764fc..7b016f540e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; @@ -138,4 +139,15 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H return builder.toString(); } + public boolean isSystemAdmin() { + return tenantId == null || EntityId.NULL_UUID.equals(tenantId.getId()); + } + + public boolean isTenantAdmin() { + return !isSystemAdmin() && (customerId == null || EntityId.NULL_UUID.equals(customerId.getId())); + } + + public boolean isCustomerUser() { + return !isSystemAdmin() && !isTenantAdmin(); + } } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java index ac9600a6a5..d7438f4fbc 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java @@ -19,5 +19,7 @@ package org.thingsboard.server.extensions.api.plugins; * @author Andrew Shvayka */ public class PluginConstants { + public static final String TELEMETRY_URL_PREFIX = "/api/plugins/telemetry"; + public static final String RPC_URL_PREFIX = "/api/plugins/rpc"; public static final String PLUGIN_URL_PREFIX = "/api/plugins"; } From c1884df254ba58552111dc2787f3ce2fac7bf8bb Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 23 Mar 2018 14:20:22 +0200 Subject: [PATCH 30/75] Fix for latest telemetry --- .../server/dao/timeseries/CassandraBaseTimeseriesDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index aba3eefbc9..cf141711a5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -450,7 +450,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem "," + ModelConstants.STRING_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.DOUBLE_VALUE_COLUMN + ")" + - " VALUES(?, ?, ?, ?, ?)"); + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); // } } return latestInsertStmt; From 68a943e2677053358bea9566e4e42a26d4eed217 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 23 Mar 2018 16:40:27 +0200 Subject: [PATCH 31/75] Fix ace editors. --- ui/src/app/components/ace-editor-fix.js | 45 +++++++++++++++++++ ui/src/app/components/js-func.directive.js | 3 ++ .../components/json-object-edit.directive.js | 5 ++- .../components/react/json-form-ace-editor.jsx | 8 ++++ .../widget/widget-config.directive.js | 5 +++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/components/ace-editor-fix.js diff --git a/ui/src/app/components/ace-editor-fix.js b/ui/src/app/components/ace-editor-fix.js new file mode 100644 index 0000000000..f68767e6e3 --- /dev/null +++ b/ui/src/app/components/ace-editor-fix.js @@ -0,0 +1,45 @@ +/* + * 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. + */ + +export default function fixAceEditor(aceEditor) { + aceEditor.$blockScrolling = Infinity; + aceEditor.on("showGutterTooltip", function (tooltip) { + if (!tooltip.isAttachedToBody) { + document.body.appendChild(tooltip.$element); //eslint-disable-line + tooltip.isAttachedToBody = true; + onElementRemoved(tooltip.$parentNode, () => { + if (tooltip.$element.parentNode != null) { + tooltip.$element.parentNode.removeChild(tooltip.$element); + } + }); + } + }); +} + +function onElementRemoved(element, callback) { + if (!document.body.contains(element)) { //eslint-disable-line + callback(); + } else { + var observer; + observer = new MutationObserver(function(mutations) { //eslint-disable-line + if (!document.body.contains(element)) { //eslint-disable-line + callback(); + observer.disconnect(); + } + }); + observer.observe(document.body, {childList: true}); //eslint-disable-line + } +} diff --git a/ui/src/app/components/js-func.directive.js b/ui/src/app/components/js-func.directive.js index f95d003e8a..33cebdebf0 100644 --- a/ui/src/app/components/js-func.directive.js +++ b/ui/src/app/components/js-func.directive.js @@ -22,6 +22,8 @@ import thingsboardToast from '../services/toast'; import thingsboardUtils from '../common/utils.service'; import thingsboardExpandFullscreen from './expand-fullscreen.directive'; +import fixAceEditor from './ace-editor-fix'; + /* eslint-disable import/no-unresolved, import/default */ import jsFuncTemplate from './js-func.tpl.html'; @@ -83,6 +85,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { scope.js_editor.session.on("change", function () { scope.cleanupJsErrors(); }); + fixAceEditor(_ace); } }; diff --git a/ui/src/app/components/json-object-edit.directive.js b/ui/src/app/components/json-object-edit.directive.js index 03ddc07db7..db0aa60bb5 100644 --- a/ui/src/app/components/json-object-edit.directive.js +++ b/ui/src/app/components/json-object-edit.directive.js @@ -19,6 +19,8 @@ import 'brace/ext/language_tools'; import 'brace/mode/json'; import 'ace-builds/src-min-noconflict/snippets/json'; +import fixAceEditor from './ace-editor-fix'; + /* eslint-disable import/no-unresolved, import/default */ import jsonObjectEditTemplate from './json-object-edit.tpl.html'; @@ -30,7 +32,7 @@ export default angular.module('thingsboard.directives.jsonObjectEdit', []) .name; /*@ngInject*/ -function JsonObjectEdit($compile, $templateCache, toast, utils) { +function JsonObjectEdit($compile, $templateCache, $document, toast, utils) { var linker = function (scope, element, attrs, ngModelCtrl) { var template = $templateCache.get(jsonObjectEditTemplate); @@ -67,6 +69,7 @@ function JsonObjectEdit($compile, $templateCache, toast, utils) { scope.json_editor.session.on("change", function () { scope.cleanupJsonErrors(); }); + fixAceEditor(_ace); } }; diff --git a/ui/src/app/components/react/json-form-ace-editor.jsx b/ui/src/app/components/react/json-form-ace-editor.jsx index 1c4c02e16f..5afd3d18f1 100644 --- a/ui/src/app/components/react/json-form-ace-editor.jsx +++ b/ui/src/app/components/react/json-form-ace-editor.jsx @@ -23,6 +23,8 @@ import FlatButton from 'material-ui/FlatButton'; import 'brace/ext/language_tools'; import 'brace/theme/github'; +import fixAceEditor from './../ace-editor-fix'; + class ThingsboardAceEditor extends React.Component { constructor(props) { @@ -31,6 +33,7 @@ class ThingsboardAceEditor extends React.Component { this.onBlur = this.onBlur.bind(this); this.onFocus = this.onFocus.bind(this); this.onTidy = this.onTidy.bind(this); + this.onLoad = this.onLoad.bind(this); var value = props.value ? props.value + '' : ''; this.state = { value: value, @@ -72,6 +75,10 @@ class ThingsboardAceEditor extends React.Component { } } + onLoad(editor) { + fixAceEditor(editor); + } + render() { const styles = reactCSS({ @@ -117,6 +124,7 @@ class ThingsboardAceEditor extends React.Component { onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur} + onLoad={this.onLoad} name={this.props.form.title} value={this.state.value} readOnly={this.props.form.readonly} diff --git a/ui/src/app/components/widget/widget-config.directive.js b/ui/src/app/components/widget/widget-config.directive.js index d0ee6a3d04..4d4d958427 100644 --- a/ui/src/app/components/widget/widget-config.directive.js +++ b/ui/src/app/components/widget/widget-config.directive.js @@ -23,6 +23,8 @@ import thingsboardJsonForm from '../json-form.directive'; import thingsboardManageWidgetActions from './action/manage-widget-actions.directive'; import 'angular-ui-ace'; +import fixAceEditor from './../ace-editor-fix'; + import './widget-config.scss'; /* eslint-disable import/no-unresolved, import/default */ @@ -72,6 +74,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout enableSnippets: true, enableBasicAutocompletion: true, enableLiveAutocompletion: true + }, + onLoad: function (_ace) { + fixAceEditor(_ace); } }; From 8b637c9e9456963e1e24ec0e6306903d2e765c1f Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 23 Mar 2018 16:54:48 +0200 Subject: [PATCH 32/75] add cassandra properties in test suite --- .../server/actors/plugin/PluginActorMessageProcessor.java | 1 + application/src/main/resources/thingsboard.yml | 4 ++-- .../quota/inmemory/HostRequestIntervalRegistry.java | 5 +++-- .../server/dao/timeseries/CassandraBaseTimeseriesDao.java | 3 --- .../thingsboard/server/dao/util/BufferedRateLimiter.java | 8 ++++++-- dao/src/test/resources/cassandra-test.properties | 5 +++++ 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java index 7dfe9a5d81..2af30d4b49 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java @@ -106,6 +106,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor try { pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); } catch (Exception ex) { + logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex); RuleToPluginMsg ruleMsg = msg.getMsg(); MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR; Integer requestId = 0; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 07face1059..c47fc28fbd 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -133,7 +133,7 @@ quota: intervalMin: 2 database: - type: "${DATABASE_TYPE:cassandra}" # cassandra OR sql + type: "${DATABASE_TYPE:sql}" # cassandra OR sql # Cassandra driver configuration parameters cassandra: @@ -226,7 +226,7 @@ caffeine: specs: relations: timeToLiveInMinutes: 1440 - maxSize: 0 + maxSize: 100000 deviceCredentials: timeToLiveInMinutes: 1440 maxSize: 100000 diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java index 8d254a08c6..3782ed22ed 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java @@ -61,13 +61,14 @@ public class HostRequestIntervalRegistry { } public long tick(String clientHostId) { + IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs)); + long currentCount = intervalCount.resetIfExpiredAndTick(); if (whiteList.contains(clientHostId)) { return 0; } else if (blackList.contains(clientHostId)) { return Long.MAX_VALUE; } - IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs)); - return intervalCount.resetIfExpiredAndTick(); + return currentCount; } public void clean() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index cf141711a5..cda4b1669b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -439,8 +439,6 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private PreparedStatement getLatestStmt() { if (latestInsertStmt == null) { -// latestInsertStmt = new PreparedStatement[DataType.values().length]; -// for (DataType type : DataType.values()) { latestInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_LATEST_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + @@ -451,7 +449,6 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem "," + ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.DOUBLE_VALUE_COLUMN + ")" + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); -// } } return latestInsertStmt; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java index de07dbfa47..2acd623a37 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger; @Component @Slf4j +@NoSqlDao public class BufferedRateLimiter implements AsyncRateLimiter { private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); @@ -113,6 +114,9 @@ public class BufferedRateLimiter implements AsyncRateLimiter { lockedFuture.cancelFuture(); return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject")); } + if(permits.get() < permitsLimit) { + reprocessQueue(); + } return lockedFuture.future; } catch (InterruptedException e) { return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Task interrupted. Reject")); @@ -130,8 +134,8 @@ public class BufferedRateLimiter implements AsyncRateLimiter { expiredCount++; } } - log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}]", maxQueueSize.getAndSet(0), - maxGrantedPermissions.getAndSet(0), expiredCount); + log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}] current granted [{}]", maxQueueSize.getAndSet(0), + maxGrantedPermissions.getAndSet(0), expiredCount, permits.get()); } private class LockedFuture { diff --git a/dao/src/test/resources/cassandra-test.properties b/dao/src/test/resources/cassandra-test.properties index 82fcbe1949..737687f053 100644 --- a/dao/src/test/resources/cassandra-test.properties +++ b/dao/src/test/resources/cassandra-test.properties @@ -47,3 +47,8 @@ cassandra.query.default_fetch_size=2000 cassandra.query.ts_key_value_partitioning=HOURS cassandra.query.max_limit_per_request=1000 +cassandra.query.buffer_size=100000 +cassandra.query.concurrent_limit=1000 +cassandra.query.permit_max_wait_time=20000 +cassandra.query.rate_limit_print_interval_ms=30000 + From 94039f943b46caa0dc86ab722fbf0006b9c4acab Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 23 Mar 2018 16:56:37 +0200 Subject: [PATCH 33/75] Squashed commit of the following: commit 8b637c9e9456963e1e24ec0e6306903d2e765c1f Author: vparomskiy Date: Fri Mar 23 16:54:48 2018 +0200 add cassandra properties in test suite --- .../server/actors/plugin/PluginActorMessageProcessor.java | 1 + application/src/main/resources/thingsboard.yml | 4 ++-- .../server/dao/timeseries/CassandraBaseTimeseriesDao.java | 3 --- .../thingsboard/server/dao/util/BufferedRateLimiter.java | 8 ++++++-- dao/src/test/resources/cassandra-test.properties | 5 +++++ 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java index 51ca652d6f..f6bf54d349 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java @@ -106,6 +106,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor try { pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); } catch (Exception ex) { + logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex); RuleToPluginMsg ruleMsg = msg.getMsg(); MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR; Integer requestId = 0; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ff5824dbfd..fc04a193e1 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -133,7 +133,7 @@ quota: intervalMin: 2 database: - type: "${DATABASE_TYPE:cassandra}" # cassandra OR sql + type: "${DATABASE_TYPE:sql}" # cassandra OR sql # Cassandra driver configuration parameters cassandra: @@ -238,7 +238,7 @@ caffeine: specs: relations: timeToLiveInMinutes: 1440 - maxSize: 0 + maxSize: 100000 deviceCredentials: timeToLiveInMinutes: 1440 maxSize: 100000 diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index cf141711a5..cda4b1669b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -439,8 +439,6 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private PreparedStatement getLatestStmt() { if (latestInsertStmt == null) { -// latestInsertStmt = new PreparedStatement[DataType.values().length]; -// for (DataType type : DataType.values()) { latestInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_LATEST_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + @@ -451,7 +449,6 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem "," + ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.DOUBLE_VALUE_COLUMN + ")" + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); -// } } return latestInsertStmt; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java index de07dbfa47..2acd623a37 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger; @Component @Slf4j +@NoSqlDao public class BufferedRateLimiter implements AsyncRateLimiter { private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); @@ -113,6 +114,9 @@ public class BufferedRateLimiter implements AsyncRateLimiter { lockedFuture.cancelFuture(); return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject")); } + if(permits.get() < permitsLimit) { + reprocessQueue(); + } return lockedFuture.future; } catch (InterruptedException e) { return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Task interrupted. Reject")); @@ -130,8 +134,8 @@ public class BufferedRateLimiter implements AsyncRateLimiter { expiredCount++; } } - log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}]", maxQueueSize.getAndSet(0), - maxGrantedPermissions.getAndSet(0), expiredCount); + log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}] current granted [{}]", maxQueueSize.getAndSet(0), + maxGrantedPermissions.getAndSet(0), expiredCount, permits.get()); } private class LockedFuture { diff --git a/dao/src/test/resources/cassandra-test.properties b/dao/src/test/resources/cassandra-test.properties index 82fcbe1949..737687f053 100644 --- a/dao/src/test/resources/cassandra-test.properties +++ b/dao/src/test/resources/cassandra-test.properties @@ -47,3 +47,8 @@ cassandra.query.default_fetch_size=2000 cassandra.query.ts_key_value_partitioning=HOURS cassandra.query.max_limit_per_request=1000 +cassandra.query.buffer_size=100000 +cassandra.query.concurrent_limit=1000 +cassandra.query.permit_max_wait_time=20000 +cassandra.query.rate_limit_print_interval_ms=30000 + From 05f92cbe656478626a83db0cbdb9da2698f91ac1 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 23 Mar 2018 19:29:48 +0200 Subject: [PATCH 34/75] Fixed installation script --- .../service/install/DefaultSystemDataLoaderService.java | 4 ++-- application/src/main/resources/thingsboard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 1ef805f3c9..908faf2315 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -187,7 +187,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Override public void loadSystemRules() throws Exception { - loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null); +// loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null); } @Override @@ -228,7 +228,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { "Raspberry Pi GPIO control sample application"); loadPlugins(Paths.get(dataDir, JSON_DIR, DEMO_DIR, PLUGINS_DIR), demoTenant.getId()); - loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId()); +// loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId()); loadDashboards(Paths.get(dataDir, JSON_DIR, DEMO_DIR, DASHBOARDS_DIR), demoTenant.getId(), null); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ff5824dbfd..c08de4f1b8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -133,7 +133,7 @@ quota: intervalMin: 2 database: - type: "${DATABASE_TYPE:cassandra}" # cassandra OR sql + type: "${DATABASE_TYPE:sql}" # cassandra OR sql # Cassandra driver configuration parameters cassandra: From 7b1a39fb68f153daf2f48c7aa7198d4deaab95d3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 23 Mar 2018 20:59:26 +0200 Subject: [PATCH 35/75] Rule Chain hover and select improvements. --- ui/src/app/locale/locale.constant.js | 1 + ui/src/app/rulechain/rulechain.controller.js | 11 +++++ ui/src/app/rulechain/rulechain.scss | 51 ++++++++++++++++---- ui/src/app/rulechain/rulechain.tpl.html | 7 +++ ui/src/app/rulechain/rulenode.tpl.html | 1 + 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index cca2a11648..a7b2b443da 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1177,6 +1177,7 @@ export default angular.module('thingsboard.locale', []) "type": "Type", "description": "Description", "delete": "Delete rule node", + "delete-selected-objects": "Delete selected nodes and connections", "rulenode-details": "Rule node details", "debug-mode": "Debug mode", "configuration": "Configuration", diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index b792f138f8..565e72b444 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -81,6 +81,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, vm.saveRuleChain = saveRuleChain; vm.revertRuleChain = revertRuleChain; + vm.objectsSelected = objectsSelected; + vm.deleteSelected = deleteSelected; + vm.keyDown = function (evt) { if (evt.keyCode === ctrlKeyCode) { vm.ctrlDown = true; @@ -632,6 +635,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }); } + function objectsSelected() { + return vm.modelservice.nodes.getSelectedNodes().length > 0 || + vm.modelservice.edges.getSelectedEdges().length > 0 + } + + function deleteSelected() { + vm.modelservice.deleteSelected(); + } } /*@ngInject*/ diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 26a5225a68..c1d1376d50 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -121,10 +121,6 @@ .fc-node { z-index: 1; outline: none; - &.fc-hover, &.fc-selected { - -webkit-filter: brightness(70%); - filter: brightness(70%); - } &.fc-dragging { z-index: 10; } @@ -132,6 +128,26 @@ padding: 0 15px; text-align: center; } + .fc-node-overlay { + position: absolute; + pointer-events: none; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: #000; + opacity: 0; + } + &.fc-hover { + .fc-node-overlay { + opacity: 0.25; + } + } + &.fc-selected { + .fc-node-overlay { + opacity: 0.25; + } + } } .fc-leftConnectors, .fc-rightConnectors { @@ -181,6 +197,7 @@ stroke: gray; stroke-width: 4; fill: transparent; + transition: stroke-width .2s; &.fc-selected { stroke: red; stroke-width: 4; @@ -232,20 +249,29 @@ .fc-edge-label { position: absolute; user-select: none; - pointer-events: none; + transition: transform .2s; opacity: 0.8; + &.fc-hover { + transform: scale(1.25); + } + &.fc-selected { + .fc-edge-label-text { + span { + border: solid red; + color: red; + } + } + } } .fc-edge-label-text { position: absolute; - left: 50%; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); white-space: nowrap; text-align: center; font-size: 14px; font-weight: 600; - top: 5px; span { border: solid 2px #003a79; border-radius: 10px; @@ -255,6 +281,13 @@ } } +.fc-select-rectangle { + border: 2px dashed #5262ff; + position: absolute; + background: rgba(20,125,255,0.1); + z-index: 2; +} + @keyframes dash { from { stroke-dashoffset: 500; diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html index 9f1141e887..eec376ea42 100644 --- a/ui/src/app/rulechain/rulechain.tpl.html +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -112,6 +112,13 @@