thingsboard-memoizeit

RuleNode annotation refactoring

3/22/2018 8:16:28 AM

Changes

rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java 50(+0 -50)

rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java 51(+0 -51)

Details

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 d9416d7..52d7d7d 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<BeanDefinition> 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<? extends Annotation> annotation) {
         List<ComponentDescriptor> 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<BeanDefinition> getBeanDefinitions(Class<? extends Annotation> 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<ComponentDescriptor> 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<ComponentDescriptor> getComponents(Set<ComponentType> types) {
         List<ComponentDescriptor> 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/resources/EmptyNodeConfig.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json
new file mode 100644
index 0000000..7a73a41
--- /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 393496a..c85d480 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 <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
+                "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
+                "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>")
 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 dff2145..faf97b4 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 <code>msg</code> property. For example <code>msg.temperature < 10;</code> " +
+                "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>")
 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 c08c3e0..7a6f9fd 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 <b>Success</b> chain, otherwise <b>Failure</b> 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 216cf28..6228206 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, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
+                "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " +
+                "<code>meta.cs.temperature</code> or <code>meta.shared.limit</code> " +
                 "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 c9cabaa..d85fb56 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")
+                "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
 
     @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 f85d019..26b7561 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")
+                "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
 
     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 2603bc5..7d9c50b 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")
+                "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
 
     @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 8ab8e18..d237df8 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 3cd7cd1..babdbc3 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.<br/> " +
+                "<code>meta</code> - is a Message metadata.<br/>" +
+                "<code>msg</code> - is a Message payload.<br/>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 2f8d152..4dd961a 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 76d0fec..973618d 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,
             '<div class="tb-rule-node-tooltip">' +
             '<div id="tooltip-content" layout="column">' +
             '<div class="tb-node-title">' + node.component.name + '</div>' +
-            '<div class="tb-node-description">' + 'Some description of node' + '</div>' +
+            '<div class="tb-node-description">' + node.component.configurationDescriptor.nodeDefinition.description + '</div>' +
+            '<div class="tb-node-details">' + node.component.configurationDescriptor.nodeDefinition.details + '</div>' +
             '</div>' +
             '</div>'
         );
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 258e232..26a5225 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