the-tall-guy-avj
the-tall-guy-avj

Reputation: 1

How to Inject Configuration from Spring Boot YAML into ThingsBoard Custom Rule Node?

I'm developing a custom rule node for ThingsBoard, and I need to configure various Kafka-related parameters through thingsboard.yml file. Instead of directly using @Value annotations, I want to read these values into a separate configuration class and then use its object in my custom rule node.

thingsboard.yml

custom:
  node.enabled: true
  config:
    bootstrap.server: localhost:9092
    topic.pattern: custom-*
    retries: 3
    batch.size: 16384
    linger.ms: 0
    buffer.memory: 33554432

CustomNodeConfig.java

package com.example;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import lombok.Data;

@Data
@Configuration
public class CustomNodeConfig {

     @Value("${custom.node.enabled}")
    private boolean enabled;

    @Value("${custom.config.bootstrap.server}")
    private String bootstrapServers;

    @Value("${custom.config.topic.pattern}")
    private String topicPattern;

    @Value("${custom.config.retries}")
    private int retries;

    @Value("${custom.config.batch.size}")
    private int batchSize;

    @Value("${custom.config.linger.ms}")
    private int linger;

    @Value("${custom.config.buffer.memory}")
    private int bufferMemory;

CustomNode.java

package com.example;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.*;

import javax.annotation.PostConstruct;

@Slf4j
@Data
@RuleNode(
    type = ComponentType.EXTERNAL,
    name = "Custom Node",
    configClazz = CustomRuleNodeConfiguration.class,
    nodeDescription = "Publish messages to Kafka server",
    nodeDetails = "Outbound message will contain response fields from Kafka in the Message Metadata.",
    uiResources = {"static/rulenode/rulenode-core-config.js"}
)
@Component
public class CustomNode implements TbNode {

    @Autowired
    private CustomNodeConfig kafkaConfig;

    @PostConstruct
    public void init() {
        log.info("CustomRuleNode initialized with retries: {}, lingerMs: {}, bootstrapServers: {}, topicPattern: {}",
                kafkaConfig.getRetries(), kafkaConfig.getLingerMs(), kafkaConfig.getBootstrapServers(), kafkaConfig.getTopicPattern());
    }

    @Override
    public void init(TbNodeConfiguration configuration) throws TbNodeException 
        log.info("CustomRuleNode configuration initialized");
    }

    @Override
    protected void onMsg(TbContext ctx, TbMsg msg) {
        log.info("Received message: {}", msg.getData());
    }

    @Override
    public void destroy() {
        log.info("CustomRuleNode destroyed!");
    }
}

The above implementation did not work as expected, the node actor does not get initialized and throws the below exception after attempting 10 retries, as the Autowired variable is not initialized.

2024-07-04 11:36:03.060  INFO 356309 --- [-dispatcher-3-3] o.t.server.actors.TbActorMailbox         : [RULE_NODE|8ad08a10-39f8-11ef-8131-5d958a56f0d4] Failed to init actor, attempt 10, going to stop attempts.

org.thingsboard.server.actors.TbActorException: Failed to init actor
    at org.thingsboard.server.actors.service.ComponentActor.initProcessor(ComponentActor.java:72) ~[classes!/:3.5.1]
    at org.thingsboard.server.actors.service.ComponentActor.init(ComponentActor.java:57) ~[classes!/:3.5.1]
    at org.thingsboard.server.actors.TbActorMailbox.tryInit(TbActorMailbox.java:66) ~[actor-3.5.1.jar!/:3.5.1]
    at org.thingsboard.server.actors.TbActorMailbox.lambda$tryInit$1(TbActorMailbox.java:85) ~[actor-3.5.1.jar!/:3.5.1]
    at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1426) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183) ~[na:na]
Caused by: java.lang.NullPointerException: null
    at org.thingsboard.rule.engine.custom.CustomNode.init(CustomNode.java:92) ~[rule-engine-components-3.5.1.jar!/:3.5.1]
    at org.thingsboard.server.actors.ruleChain.RuleNodeActorMessageProcessor.initComponent(RuleNodeActorMessageProcessor.java:173) ~[classes!/:3.5.1]
    at org.thingsboard.server.actors.ruleChain.RuleNodeActorMessageProcessor.start(RuleNodeActorMessageProcessor.java:64) ~[classes!/:3.5.1]
    at org.thingsboard.server.actors.service.ComponentActor.initProcessor(ComponentActor.java:63) ~[classes!/:3.5.1]
    ... 9 common frames omitted

Questions

  1. Is there a better or more standard approach to manage configuration for a custom rule node in ThingsBoard using Spring Boot?
  2. Is my approach of using a separate configuration class (CustomNodeConfig) and autowiring it into the rule node (CustomNode) correct for injecting configuration values from application.yml?

Upvotes: 0

Views: 75

Answers (1)

Aldo Miranda
Aldo Miranda

Reputation: 1

I don't think you can get access to the Spring application context from a TbNode object as the whole rule chain execution is sandboxed and all these objects are not instantiated by Spring. The best you could do, I think, is add your parameter properties in the configuration class CustomRuleNodeConfiguration and fill in the values from the rule chain editor.

Upvotes: 0

Related Questions