All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.automatiko.engine.codegen.process.MessageProducerGenerator Maven / Gradle / Ivy

The newest version!

package io.automatiko.engine.codegen.process;

import static com.github.javaparser.StaticJavaParser.parse;
import static io.automatiko.engine.codegen.CodeGenConstants.AMQP_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.CAMEL_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.DIRECT_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.FUNCTION_FLOW_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.HTTP_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.JMS_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.KAFKA_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.MQTT_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.OUTGOING_PROP_PREFIX;
import static io.automatiko.engine.codegen.CodeGenConstants.PULSAR_CONNECTOR;
import static io.automatiko.engine.codegen.CodeGenConstants.RABBITMQ_CONNECTOR;
import static io.automatiko.engine.codegen.CodegenUtils.interpolateEventTypes;
import static io.automatiko.engine.codegen.CodegenUtils.interpolateTypes;

import java.util.Map.Entry;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.AssignExpr;
import com.github.javaparser.ast.expr.CastExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.expr.VariableDeclarationExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.type.ClassOrInterfaceType;

import io.automatiko.engine.api.Functions;
import io.automatiko.engine.api.definition.process.Process;
import io.automatiko.engine.api.definition.process.WorkflowProcess;
import io.automatiko.engine.codegen.BodyDeclarationComparator;
import io.automatiko.engine.codegen.CodegenUtils;
import io.automatiko.engine.codegen.GeneratorContext;
import io.automatiko.engine.codegen.ImportsOrganizer;
import io.automatiko.engine.codegen.di.DependencyInjectionAnnotator;
import io.automatiko.engine.services.execution.BaseFunctions;
import io.automatiko.engine.services.utils.StringUtils;
import io.automatiko.engine.workflow.base.core.context.variable.Variable;
import io.automatiko.engine.workflow.base.core.context.variable.VariableScope;
import io.automatiko.engine.workflow.base.core.datatype.impl.type.ObjectDataType;
import io.automatiko.engine.workflow.compiler.canonical.TriggerMetaData;
import io.automatiko.engine.workflow.process.executable.core.Metadata;

public class MessageProducerGenerator {

    private static final String EVENT_DATA_VAR = "eventData";

    private final String relativePath;

    private GeneratorContext context;

    private WorkflowProcess process;
    private String workflowType;
    private final String packageName;
    private final String resourceClazzName;
    private final String modelClazzName;
    private String processId;
    private final String processName;
    private final String classPrefix;
    private final String messageDataEventClassName;
    private DependencyInjectionAnnotator annotator;

    private TriggerMetaData trigger;

    public MessageProducerGenerator(String workflowType, GeneratorContext context, WorkflowProcess process, String modelfqcn,
            String processfqcn, String messageDataEventClassName, TriggerMetaData trigger) {
        this.workflowType = workflowType;
        this.context = context;
        this.process = process;
        this.trigger = trigger;
        this.packageName = process.getPackageName();
        this.processId = process.getId();
        this.processName = processId.substring(processId.lastIndexOf('.') + 1);
        this.classPrefix = StringUtils.capitalize(processName) + CodegenUtils.version(process.getVersion());
        this.resourceClazzName = classPrefix + "MessageProducer_" + trigger.getOwnerId();
        this.relativePath = packageName.replace(".", "/") + "/" + resourceClazzName + ".java";
        this.messageDataEventClassName = messageDataEventClassName;
        this.modelClazzName = modelfqcn;
    }

    public MessageProducerGenerator withDependencyInjection(DependencyInjectionAnnotator annotator) {
        this.annotator = annotator;
        return this;
    }

    public String className() {
        return resourceClazzName;
    }

    public String generatedFilePath() {
        return relativePath;
    }

    protected boolean useInjection() {
        return this.annotator != null;
    }

    protected void appendConnectorSpecificProperties(String connector) {
        String sanitizedName = CodegenUtils.triggerSanitizedName(trigger, process.getVersion());
        if (connector.equals(MQTT_CONNECTOR)) {

            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".topic",
                    (String) trigger.getContext("topic", trigger.getName()));
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".host", "${mqtt.server:localhost}");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".port", "${mqtt.port:1883}");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".client-id",
                    classPrefix + "-producer");

            context.addInstruction(
                    "Properties for MQTT based message event '" + trigger.getDescription() + "'");
            context.addInstruction(
                    "\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                            + ".topic' should be used to configure MQTT topic defaults to '"
                            + trigger.getContext("topic", trigger.getName()) + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".host' should be used to configure MQTT host that defaults to localhost");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".port' should be used to configure MQTT port that defaults to 1883");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".client-id' should be used to configure MQTT client id that defaults to '" + classPrefix
                    + "-producer'");
        } else if (connector.equals(CAMEL_CONNECTOR)) {
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".endpoint-uri",
                    (String) trigger.getContext("url", ""));
            context.setApplicationProperty("quarkus.automatiko.messaging.as-cloudevents", "false");
            context.addInstruction(
                    "Properties for Apache Camel based message event '" + trigger.getDescription() + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".endpoint-uri' should be used to configure Apache Camel location");
        } else if (connector.equals(KAFKA_CONNECTOR)) {

            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".bootstrap.servers",
                    "${kafka.bootstrap.servers:localhost\\\\:9092}");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".topic",
                    (String) trigger.getContext("topic", trigger.getName()));
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".key.serializer",
                    "org.apache.kafka.common.serialization.StringSerializer");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".value.serializer",
                    "org.apache.kafka.common.serialization.StringSerializer");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".group.id",
                    classPrefix + "-consumer");
            context.setApplicationProperty("quarkus.automatiko.messaging.as-cloudevents",
                    isServerlessProcess() ? "true" : "false");
            context.addInstruction(
                    "Properties for Apache Kafka based message event '" + trigger.getDescription() + "'");
            context.addInstruction(
                    "\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                            + ".topic' should be used to configure Kafka topic defaults to '"
                            + trigger.getContext("topic", trigger.getName()) + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".bootstrap.servers' should be used to configure Kafka bootstrap servers host that defaults to localhost:9092");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".key.serializer' should be used to configure key deserializer port that defaults to StringSerializer");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".value.serializer' should be used to configure key deserializer port that defaults to StringSerializer");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".group.id' should be used to configure Kafka group id that defaults to '" + classPrefix
                    + "-consumer'");
        } else if (connector.equals(JMS_CONNECTOR)) {
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");
            context.setApplicationProperty("quarkus.index-dependency.sjms.group-id", "io.smallrye.reactive");
            context.setApplicationProperty("quarkus.index-dependency.sjms.artifact-id", "smallrye-reactive-messaging-jms");

            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".destination", sanitizedName.toUpperCase());
            context.setApplicationProperty("quarkus.automatiko.messaging.as-cloudevents",
                    isServerlessProcess() ? "true" : "false");
            context.addInstruction(
                    "Properties for JMS based message event '" + trigger.getDescription() + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".destination' should be used to configure destination (queue or topic) name defaults to '"
                    + context.getApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".destination")
                            .orElse(sanitizedName.toUpperCase())
                    + "'");
        } else if (connector.equals(AMQP_CONNECTOR)) {
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".address", sanitizedName.toUpperCase());
            context.setApplicationProperty("quarkus.automatiko.messaging.as-cloudevents",
                    isServerlessProcess() ? "true" : "false");
            context.addInstruction(
                    "Properties for AMQP based message event '" + trigger.getDescription() + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".address' should be used to configure address name, defaults to "
                    + context.getApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".address")
                            .orElse(sanitizedName.toUpperCase())
                    + "'");
        } else if (connector.equals(HTTP_CONNECTOR)) {
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".url",
                    (String) trigger.getContext("url", "http://localhost:8080/" + sanitizedName));
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");
            //            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".serializer",
            //                    "io.quarkus.reactivemessaging.http.runtime.serializers.StringSerializer");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".method", "POST");
            context.setApplicationProperty("quarkus.automatiko.messaging.as-cloudevents",
                    isServerlessProcess() ? "true" : "false");
            context.addInstruction(
                    "Properties for HTTP based message event '" + trigger.getDescription() + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".url' should be used to configure location of the service to call via HTTP, defaults to "
                    + context.getApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".url")
                            .orElse((String) trigger.getContext("url", "http://localhost:8080/" + sanitizedName))
                    + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".method' should be used to configure HTTP method of the service to call, defaults to "
                    + context.getApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".method")
                            .orElse("POST")
                    + "'");
        } else if (connector.equals(PULSAR_CONNECTOR)) {

            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");

            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".topic",
                    (String) trigger.getContext("topic", trigger.getName()));
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".schema",
                    "STRING");
            context.setApplicationProperty("quarkus.automatiko.messaging.as-cloudevents",
                    isServerlessProcess() ? "true" : "false");
            context.addInstruction(
                    "Properties for Apache Pulsar based message event '" + trigger.getDescription() + "'");
            context.addInstruction(
                    "\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                            + ".topic' should be used to configure Kafka topic defaults to '"
                            + trigger.getContext("topic", trigger.getName()) + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".serviceUrl' should be used to configure Pulsar host that defaults to pulsar://localhost:6650");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".schema' should be used to configure schema that defaults to STRING");
        } else if (connector.equals(RABBITMQ_CONNECTOR)) {
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".merge", "true");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".queue.name", sanitizedName.toUpperCase());
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".exchange.name", "\\\"\\\"");
            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".default-routing-key",
                    "${" + OUTGOING_PROP_PREFIX + sanitizedName + ".queue.name" + "}");
            context.setApplicationProperty("quarkus.automatiko.messaging.as-cloudevents",
                    isServerlessProcess() ? "true" : "false");
            context.addInstruction(
                    "Properties for RabbitMQ based message event '" + trigger.getDescription() + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".queue.name' should be used to configure queue name, defaults to "
                    + context.getApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".queue.name")
                            .orElse(sanitizedName.toUpperCase())
                    + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".exchange.name' should be used to configure exchange name, defaults to '"
                    + context.getApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".exchange.name")
                            .orElse("\"\"")
                    + "'");
            context.addInstruction("\t'" + OUTGOING_PROP_PREFIX + sanitizedName
                    + ".default-routing-key' should be used to configure exchange name, defaults to '"
                    + context.getApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".default-routing-key")
                            .orElse("${" + OUTGOING_PROP_PREFIX + sanitizedName + ".queue.name" + "}")
                    + "'");
        }
    }

    protected String producerTemplate(String connector) {
        if ((connector.equals("unknown") || connector.equals(FUNCTION_FLOW_CONNECTOR))
                && workflowType.equals(Process.FUNCTION_FLOW_TYPE)) {
            return "/class-templates/FunctionFlowMessageProducerTemplate.java";
        } else if (connector.equals(MQTT_CONNECTOR)) {
            return "/class-templates/MQTTMessageProducerTemplate.java";
        } else if (connector.equals(CAMEL_CONNECTOR)) {
            return "/class-templates/CamelMessageProducerTemplate.java";
        } else if (connector.equals(KAFKA_CONNECTOR)) {
            return "/class-templates/KafkaMessageProducerTemplate.java";
        } else if (connector.equals(JMS_CONNECTOR)) {
            return "/class-templates/JMSMessageProducerTemplate.java";
        } else if (connector.equals(AMQP_CONNECTOR)) {
            return "/class-templates/AMQPMessageProducerTemplate.java";
        } else if (connector.equals(HTTP_CONNECTOR)) {
            return "/class-templates/HTTPMessageProducerTemplate.java";
        } else if (connector.equals(PULSAR_CONNECTOR)) {
            return "/class-templates/PulsarMessageProducerTemplate.java";
        } else if (connector.equals(RABBITMQ_CONNECTOR)) {
            return "/class-templates/RabbitMQMessageProducerTemplate.java";
        } else if (connector.equals(DIRECT_CONNECTOR)) {
            return "/class-templates/DirectMessageProducerTemplate.java";
        } else {
            return "/class-templates/MessageProducerTemplate.java";
        }
    }

    public String generate() {

        String sanitizedName = CodegenUtils.triggerSanitizedName(trigger, process.getVersion());
        String connector = CodegenUtils.getConnector(OUTGOING_PROP_PREFIX + sanitizedName + ".connector", context,
                (String) trigger.getContext("connector"));
        if (connector != null && !DIRECT_CONNECTOR.equals(connector)) {

            context.setApplicationProperty(OUTGOING_PROP_PREFIX + sanitizedName + ".connector", connector);

            appendConnectorSpecificProperties(connector);
        }

        CompilationUnit clazz = parse(
                this.getClass().getResourceAsStream(producerTemplate(connector)));
        clazz.setPackageDeclaration(process.getPackageName());

        // add functions so they can be easily accessed in message producer classes
        clazz.addImport(new ImportDeclaration(BaseFunctions.class.getCanonicalName(), true, true));
        context.getBuildContext().classThatImplement(Functions.class.getCanonicalName())
                .forEach(c -> clazz.addImport(new ImportDeclaration(c, true, true)));

        VariableScope processVariableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                .getDefaultContext(VariableScope.VARIABLE_SCOPE);

        for (Variable var : processVariableScope.getVariables()) {
            if (var.getType() instanceof ObjectDataType && var.getType().getClassType() != null) {
                Class varType = var.getType().getClassType();

                clazz.addImport(varType.getCanonicalName());
            }
        }

        ClassOrInterfaceDeclaration template = clazz.findFirst(ClassOrInterfaceDeclaration.class).get();
        template.setName(resourceClazzName);

        template.findAll(ClassOrInterfaceType.class).forEach(cls -> interpolateTypes(cls, trigger.getDataType()));
        template.findAll(ClassOrInterfaceType.class).forEach(cls -> interpolateEventTypes(cls, messageDataEventClassName));
        template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("produce"))
                .forEach(md -> {

                    md.getParameters().stream().filter(p -> p.getNameAsString().equals(EVENT_DATA_VAR))
                            .forEach(p -> p.setType(trigger.getDataType()));

                    if (context.getBuildContext().isTracingSupported()) {
                        md.addAnnotation("io.opentelemetry.instrumentation.annotations.WithSpan");
                    }
                });
        template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("configure"))
                .forEach(md -> md.addAnnotation("jakarta.annotation.PostConstruct"));
        template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("marshall"))
                .forEach(md -> {
                    md.getParameters().stream().filter(p -> p.getNameAsString().equals(EVENT_DATA_VAR))
                            .forEach(p -> p.setType(trigger.getDataType()));
                    md.findAll(ClassOrInterfaceType.class).forEach(
                            t -> t.setName(t.getNameAsString().replace("$DataEventType$", messageDataEventClassName)));
                });

        template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("convert"))
                .forEach(md -> {
                    md.setType(md.getTypeAsString().replace("$DataType$", trigger.getDataType()));

                    md.findAll(CastExpr.class)
                            .forEach(c -> c.setType(c.getTypeAsString().replace("$DataType$", trigger.getDataType())));
                    md.findAll(ClassOrInterfaceType.class)
                            .forEach(t -> t.setName(t.getNameAsString().replace("$DataType$", trigger.getDataType())));

                });

        // used by MQTT to get topic name based on expression
        String topicExpression = (String) trigger.getContext("topicExpression");
        if (topicExpression != null) {
            template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("topic"))
                    .forEach(md -> {
                        BlockStmt body = new BlockStmt();

                        ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, String.class.getCanonicalName());

                        if (topicExpression.contains("id")) {
                            VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "id");
                            body.addStatement(new AssignExpr(idField,
                                    new MethodCallExpr(new NameExpr("pi"), "getId"), AssignExpr.Operator.ASSIGN));
                        }

                        if (topicExpression.contains("businessKey")) {
                            VariableDeclarationExpr businessKeyField = new VariableDeclarationExpr(stringType, "businessKey");
                            body.addStatement(new AssignExpr(businessKeyField,
                                    new MethodCallExpr(new NameExpr("pi"), "getCorrelationKey"), AssignExpr.Operator.ASSIGN));
                        }
                        VariableScope variableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                                .getDefaultContext(VariableScope.VARIABLE_SCOPE);

                        for (Variable var : variableScope.getVariables()) {

                            if (topicExpression.contains(var.getSanitizedName())) {
                                ClassOrInterfaceType varType = new ClassOrInterfaceType(null, var.getType().getStringType());
                                VariableDeclarationExpr v = new VariableDeclarationExpr(
                                        varType,
                                        var.getSanitizedName());
                                body.addStatement(new AssignExpr(v,
                                        new CastExpr(varType,
                                                new MethodCallExpr(new MethodCallExpr(new NameExpr("pi"), "getVariables"),
                                                        "get")
                                                                .addArgument(new StringLiteralExpr(var.getName()))),
                                        AssignExpr.Operator.ASSIGN));
                            }
                        }
                        body.addStatement(new ReturnStmt(new NameExpr(topicExpression)));
                        md.setBody(body);
                    });
        }
        // used by AMQP to get address name based on expression
        String addressExpression = (String) trigger.getContext("addressExpression");
        if (addressExpression != null) {
            template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("address"))
                    .forEach(md -> {
                        BlockStmt body = new BlockStmt();

                        ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, String.class.getCanonicalName());

                        if (addressExpression.contains("id")) {
                            VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "id");
                            body.addStatement(new AssignExpr(idField,
                                    new MethodCallExpr(new NameExpr("pi"), "getId"), AssignExpr.Operator.ASSIGN));
                        }

                        if (addressExpression.contains("businessKey")) {
                            VariableDeclarationExpr businessKeyField = new VariableDeclarationExpr(stringType, "businessKey");
                            body.addStatement(new AssignExpr(businessKeyField,
                                    new MethodCallExpr(new NameExpr("pi"), "getCorrelationKey"), AssignExpr.Operator.ASSIGN));
                        }
                        VariableScope variableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                                .getDefaultContext(VariableScope.VARIABLE_SCOPE);

                        for (Variable var : variableScope.getVariables()) {

                            if (addressExpression.contains(var.getSanitizedName())) {
                                ClassOrInterfaceType varType = new ClassOrInterfaceType(null, var.getType().getStringType());
                                VariableDeclarationExpr v = new VariableDeclarationExpr(
                                        varType,
                                        var.getSanitizedName());
                                body.addStatement(new AssignExpr(v,
                                        new CastExpr(varType,
                                                new MethodCallExpr(new MethodCallExpr(new NameExpr("pi"), "getVariables"),
                                                        "get")
                                                                .addArgument(new StringLiteralExpr(var.getName()))),
                                        AssignExpr.Operator.ASSIGN));
                            }
                        }
                        body.addStatement(new ReturnStmt(new NameExpr(addressExpression)));
                        md.setBody(body);
                    });
        }

        // used by FunctionFlow to set subject (used by reply to)
        String subjectExpression = (String) trigger.getContext("subjectExpression");
        if (subjectExpression != null) {
            template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("subject"))
                    .forEach(md -> {
                        BlockStmt body = new BlockStmt();

                        ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, String.class.getCanonicalName());

                        if (subjectExpression.contains("id")) {
                            VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "id");
                            body.addStatement(new AssignExpr(idField,
                                    new MethodCallExpr(new NameExpr("pi"), "getId"), AssignExpr.Operator.ASSIGN));
                        }

                        if (subjectExpression.contains("businessKey")) {
                            VariableDeclarationExpr businessKeyField = new VariableDeclarationExpr(stringType, "businessKey");
                            body.addStatement(new AssignExpr(businessKeyField,
                                    new MethodCallExpr(new NameExpr("pi"), "getCorrelationKey"), AssignExpr.Operator.ASSIGN));
                        }
                        if (subjectExpression.contains("referenceId")) {
                            VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "referenceId");
                            body.addStatement(new AssignExpr(idField,
                                    new MethodCallExpr(new NameExpr("pi"), "getReferenceId"), AssignExpr.Operator.ASSIGN));
                        }
                        VariableScope variableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                                .getDefaultContext(VariableScope.VARIABLE_SCOPE);

                        for (Variable var : variableScope.getVariables()) {

                            if (subjectExpression.contains(var.getSanitizedName())) {
                                ClassOrInterfaceType varType = new ClassOrInterfaceType(null, var.getType().getStringType());
                                VariableDeclarationExpr v = new VariableDeclarationExpr(
                                        varType,
                                        var.getSanitizedName());
                                body.addStatement(new AssignExpr(v,
                                        new CastExpr(varType,
                                                new MethodCallExpr(new MethodCallExpr(new NameExpr("pi"), "getVariables"),
                                                        "get")
                                                                .addArgument(new StringLiteralExpr(var.getName()))),
                                        AssignExpr.Operator.ASSIGN));
                            }
                        }
                        body.addStatement(new ReturnStmt(new NameExpr(subjectExpression)));
                        md.setBody(body);
                    });
        }
        // Camal or HTTP headers
        template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("headers"))
                .forEach(md -> {
                    StringBuilder allHeaderValues = new StringBuilder();
                    for (Entry entry : trigger.getContext().entrySet()) {

                        if (entry.getKey().startsWith("Camel") || entry.getKey().startsWith("HTTP")) {
                            allHeaderValues.append(entry.getValue().toString()).append(" ");
                        }
                    }
                    String allHeaderValuesStr = allHeaderValues.toString();
                    BlockStmt body = new BlockStmt();

                    ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, String.class.getCanonicalName());

                    if (allHeaderValuesStr.contains("id")) {
                        VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "id");
                        body.addStatement(new AssignExpr(idField,
                                new MethodCallExpr(new NameExpr("pi"), "getId"), AssignExpr.Operator.ASSIGN));
                    }

                    if (allHeaderValuesStr.contains("businessKey")) {
                        VariableDeclarationExpr businessKeyField = new VariableDeclarationExpr(stringType, "businessKey");
                        body.addStatement(new AssignExpr(businessKeyField,
                                new MethodCallExpr(new NameExpr("pi"), "getCorrelationKey"), AssignExpr.Operator.ASSIGN));
                    }
                    VariableScope variableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                            .getDefaultContext(VariableScope.VARIABLE_SCOPE);

                    for (Variable var : variableScope.getVariables()) {

                        if (allHeaderValuesStr.contains(var.getSanitizedName())) {
                            ClassOrInterfaceType varType = new ClassOrInterfaceType(null, var.getType().getStringType());
                            VariableDeclarationExpr v = new VariableDeclarationExpr(
                                    varType,
                                    var.getSanitizedName());
                            body.addStatement(new AssignExpr(v,
                                    new CastExpr(varType,
                                            new MethodCallExpr(new MethodCallExpr(new NameExpr("pi"), "getVariables"),
                                                    "get")
                                                            .addArgument(new StringLiteralExpr(var.getName()))),
                                    AssignExpr.Operator.ASSIGN));
                        }
                    }

                    for (Entry entry : trigger.getContext().entrySet()) {

                        if (entry.getKey().startsWith("Camel")) {

                            body.addStatement(new MethodCallExpr(new NameExpr("metadata"), "putHeader")
                                    .addArgument(new StringLiteralExpr(entry.getKey()))
                                    .addArgument(new NameExpr(entry.getValue().toString())));
                        } else if (entry.getKey().startsWith("HTTP")) {

                            body.addStatement(new MethodCallExpr(new NameExpr("builder"), "addHeader")
                                    .addArgument(new StringLiteralExpr(entry.getKey().replaceFirst("HTTP", "")))
                                    .addArgument(new NameExpr(entry.getValue().toString())));
                        }

                    }

                    if (!md.getTypeAsString().equalsIgnoreCase("void")) {
                        body.addStatement(new ReturnStmt(new NameExpr("metadata")));
                    }
                    md.setBody(body);
                });

        // JMS properties
        template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("properties"))
                .forEach(md -> {
                    StringBuilder allHeaderValues = new StringBuilder();
                    for (Entry entry : trigger.getContext().entrySet()) {

                        if (entry.getKey().startsWith("JMS")) {
                            allHeaderValues.append(entry.getValue().toString()).append(" ");
                        }
                    }
                    String allHeaderValuesStr = allHeaderValues.toString();
                    BlockStmt body = new BlockStmt();

                    ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, String.class.getCanonicalName());

                    if (allHeaderValuesStr.contains("id")) {
                        VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "id");
                        body.addStatement(new AssignExpr(idField,
                                new MethodCallExpr(new NameExpr("pi"), "getId"), AssignExpr.Operator.ASSIGN));
                    }

                    if (allHeaderValuesStr.contains("businessKey")) {
                        VariableDeclarationExpr businessKeyField = new VariableDeclarationExpr(stringType, "businessKey");
                        body.addStatement(new AssignExpr(businessKeyField,
                                new MethodCallExpr(new NameExpr("pi"), "getCorrelationKey"), AssignExpr.Operator.ASSIGN));
                    }
                    VariableScope variableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                            .getDefaultContext(VariableScope.VARIABLE_SCOPE);

                    for (Variable var : variableScope.getVariables()) {

                        if (allHeaderValuesStr.contains(var.getSanitizedName())) {
                            ClassOrInterfaceType varType = new ClassOrInterfaceType(null, var.getType().getStringType());
                            VariableDeclarationExpr v = new VariableDeclarationExpr(
                                    varType,
                                    var.getSanitizedName());
                            body.addStatement(new AssignExpr(v,
                                    new CastExpr(varType,
                                            new MethodCallExpr(new MethodCallExpr(new NameExpr("pi"), "getVariables"),
                                                    "get")
                                                            .addArgument(new StringLiteralExpr(var.getName()))),
                                    AssignExpr.Operator.ASSIGN));
                        }
                    }

                    for (Entry entry : trigger.getContext().entrySet()) {

                        if (entry.getKey().startsWith("JMS")) {
                            body.addStatement(new MethodCallExpr(new NameExpr("builder"), "with")
                                    .addArgument(new StringLiteralExpr(entry.getKey().replaceFirst("JMS", "")))
                                    .addArgument(new NameExpr(entry.getValue().toString())));
                        }

                    }

                    body.addStatement(new ReturnStmt(new NameExpr("builder")));
                    md.setBody(body);
                });

        // used by Kafka/Pulsar to get key name based on expression
        String keyExpression = (String) trigger.getContext("keyExpression");
        if (keyExpression != null) {
            template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("key"))
                    .forEach(md -> {
                        BlockStmt body = new BlockStmt();

                        ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, String.class.getCanonicalName());

                        if (keyExpression.contains("id")) {
                            VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "id");
                            body.addStatement(new AssignExpr(idField,
                                    new MethodCallExpr(new NameExpr("pi"), "getId"), AssignExpr.Operator.ASSIGN));
                        }

                        if (keyExpression.contains("businessKey")) {
                            VariableDeclarationExpr businessKeyField = new VariableDeclarationExpr(stringType, "businessKey");
                            body.addStatement(new AssignExpr(businessKeyField,
                                    new MethodCallExpr(new NameExpr("pi"), "getCorrelationKey"), AssignExpr.Operator.ASSIGN));
                        }
                        VariableScope variableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                                .getDefaultContext(VariableScope.VARIABLE_SCOPE);

                        for (Variable var : variableScope.getVariables()) {

                            if (keyExpression.contains(var.getSanitizedName())) {
                                ClassOrInterfaceType varType = new ClassOrInterfaceType(null, var.getType().getStringType());
                                VariableDeclarationExpr v = new VariableDeclarationExpr(
                                        varType,
                                        var.getSanitizedName());
                                body.addStatement(new AssignExpr(v,
                                        new CastExpr(varType,
                                                new MethodCallExpr(new MethodCallExpr(new NameExpr("pi"), "getVariables"),
                                                        "get")
                                                                .addArgument(new StringLiteralExpr(var.getName()))),
                                        AssignExpr.Operator.ASSIGN));
                            }
                        }
                        body.addStatement(new ReturnStmt(new NameExpr(keyExpression)));
                        md.setBody(body);
                    });
        }

        // used by RabbitMQ to get routing key name based on expression
        String routingKeyExpression = (String) trigger.getContext("routingKeyExpression");
        if (routingKeyExpression != null) {
            template.findAll(MethodDeclaration.class).stream().filter(md -> md.getNameAsString().equals("routingKey"))
                    .forEach(md -> {
                        BlockStmt body = new BlockStmt();

                        ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, String.class.getCanonicalName());

                        if (addressExpression.contains("id")) {
                            VariableDeclarationExpr idField = new VariableDeclarationExpr(stringType, "id");
                            body.addStatement(new AssignExpr(idField,
                                    new MethodCallExpr(new NameExpr("pi"), "getId"), AssignExpr.Operator.ASSIGN));
                        }

                        if (addressExpression.contains("businessKey")) {
                            VariableDeclarationExpr businessKeyField = new VariableDeclarationExpr(stringType, "businessKey");
                            body.addStatement(new AssignExpr(businessKeyField,
                                    new MethodCallExpr(new NameExpr("pi"), "getCorrelationKey"), AssignExpr.Operator.ASSIGN));
                        }
                        VariableScope variableScope = (VariableScope) ((io.automatiko.engine.workflow.process.core.WorkflowProcess) process)
                                .getDefaultContext(VariableScope.VARIABLE_SCOPE);

                        for (Variable var : variableScope.getVariables()) {

                            if (addressExpression.contains(var.getSanitizedName())) {
                                ClassOrInterfaceType varType = new ClassOrInterfaceType(null, var.getType().getStringType());
                                VariableDeclarationExpr v = new VariableDeclarationExpr(
                                        varType,
                                        var.getSanitizedName());
                                body.addStatement(new AssignExpr(v,
                                        new CastExpr(varType,
                                                new MethodCallExpr(new MethodCallExpr(new NameExpr("pi"), "getVariables"),
                                                        "get")
                                                                .addArgument(new StringLiteralExpr(var.getName()))),
                                        AssignExpr.Operator.ASSIGN));
                            }
                        }
                        body.addStatement(new ReturnStmt(new NameExpr(routingKeyExpression)));
                        md.setBody(body);
                    });
        }

        template.findAll(MethodDeclaration.class)
                .forEach(md -> {
                    md.findAll(StringLiteralExpr.class)
                            .forEach(str -> str.setString(str.asString().replace("$Trigger$", trigger.getName())));
                });
        template.findAll(MethodDeclaration.class)
                .forEach(md -> {
                    md.findAll(StringLiteralExpr.class)
                            .forEach(str -> str.setString(str.asString().replace("$TriggerType$",
                                    (String) trigger.getContext(Metadata.TRIGGER_TYPE_ATTR, trigger.getName()))));
                });

        if (useInjection()) {
            annotator.withApplicationComponent(template);

            template.findAll(FieldDeclaration.class, fd -> fd.getVariable(0).getNameAsString().equals("emitter"))
                    .forEach(emitterField -> {
                        annotator.withInjection(emitterField);
                        annotator.withOutgoingMessage(emitterField, sanitizedName);
                    });
            template.findAll(FieldDeclaration.class, fd -> fd.getVariables().get(0).getNameAsString().equals("converter"))
                    .forEach(fd -> {
                        annotator.withInjection(fd);
                        fd.getVariable(0)
                                .setType(fd.getVariable(0).getTypeAsString().replace("$DataType$", trigger.getDataType()));
                    });

            template.findAll(FieldDeclaration.class, fd -> fd.getVariable(0).getNameAsString().equals("useCloudEvents"))
                    .forEach(fd -> annotator.withConfigInjection(fd, "quarkus.automatiko.messaging.as-cloudevents"));

            template.findAll(FieldDeclaration.class, fd -> fd.getVariable(0).getNameAsString().equals("useCloudEventsBinary"))
                    .forEach(fd -> annotator.withConfigInjection(fd, "quarkus.automatiko.messaging.as-cloudevents-binary"));

        }
        // add connector and message name as static fields of the class
        FieldDeclaration connectorField = new FieldDeclaration().setStatic(true).setFinal(true)
                .addVariable(new VariableDeclarator(new ClassOrInterfaceType(null, "String"), "CONNECTOR",
                        new StringLiteralExpr(connector)));
        template.addMember(connectorField);

        FieldDeclaration messageNameField = new FieldDeclaration().setStatic(true).setFinal(true)
                .addVariable(new VariableDeclarator(new ClassOrInterfaceType(null, "String"), "MESSAGE",
                        new StringLiteralExpr(trigger.getName())));
        template.addMember(messageNameField);

        if (workflowType.equals(Process.FUNCTION_FLOW_TYPE)) {
            String destination = (String) trigger.getContext("functionType", sanitizedName);
            String sourcePrefix = process.getPackageName() + "." + processId + "." + sanitizedName;

            template.findAll(StringLiteralExpr.class).forEach(vv -> {
                String s = vv.getValue();
                String interpolated = s.replace("$destination$", destination);
                interpolated = interpolated.replace("$sourcePrefix$", sourcePrefix);
                vv.setString(interpolated);
            });
        }

        template.getMembers().sort(new BodyDeclarationComparator());
        ImportsOrganizer.organize(clazz);
        return clazz.toString();
    }

    private boolean isServerlessProcess() {
        return (boolean) process.getMetaData().getOrDefault("IsServerlessWorkflow", false);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy