Mule 4 will be released soon. Along with Mule 4 a new Mule SDK is released which can be used to extend the functionality of Mule with custom modules. The Mule SDK replaces Devkit for developing connectors.

Documentation on the Mule SDK can so far be found on the About the Mule SDK page.

The Mule SDK is not tightly bound to Anypoint Studio in the same way that Devkit is. The custom modules is hooking into the Mule Runtime by annotations and class inheritance. Instead of having to install the custom module in Anypoint Studio, you just have to add it as a Maven dependency.

Setting up a project

A maven archetype is provided as a starting point. At the moment it has some flaws. Looking for newer versions of the archetype is probably a good idea.

The archetype project can be found on the project GitHub page.

mvn archetype:generate \
  -DarchetypeGroupId=org.mule.extensions \
  -DarchetypeArtifactId=mule-extensions-archetype \
  -DarchetypeVersion=1.2.0-SNAPSHOT \
  -DgroupId=com.redpilllinpro.mule.demo \
  -DartifactId=rplp-extension \
  -DextensionName=RPLP

This creates a Maven project structure with an example component and unit test.

The connector

A custom connector consist of a number of classes connected with annotations.

The extension is defined by a class with the Extension annotation. A Configurations annotation points to the main configuration class.

package com.redpilllinpro.mule.demo;

import org.mule.runtime.extension.api.annotation.Extension;
import org.mule.runtime.extension.api.annotation.Configurations;
import org.mule.runtime.extension.api.annotation.dsl.xml.Xml;

@Xml(prefix = "rplp")
@Extension(name = "RPLP")
@Configurations(RPLPConfiguration.class)
public class RPLPExtension {
}

A configuration class defines the parameters that are to be defined in the config element of the module. By using the Operations and ConnectionProviders annotations this class also points to the operations that the extension implements and to the connection providers.

package com.redpilllinpro.mule.demo;

import org.mule.runtime.extension.api.annotation.Operations;
import org.mule.runtime.extension.api.annotation.connectivity.ConnectionProviders;
import org.mule.runtime.extension.api.annotation.param.Parameter;

@Operations(RPLPOperations.class)
@ConnectionProviders(RPLPConnectionProvider.class)
public class RPLPConfiguration {

  @Parameter
  private String configId;

  public String getConfigId(){
    return configId;
  }
}

A connection provider implements one of the supplied connection providers, in this case PoolingConnectionProvider. Other options are CachedConnectionProvider and ConnectionProvider. The connect, disconnect and validate methods are overridden to provide functionality specific to this connection. Parameters specified in this class with the Parameter annotation are used as parameters to the connection in the config element of the component.

package com.redpilllinpro.mule.demo;

import org.mule.runtime.api.connection.ConnectionException;
import org.mule.runtime.extension.api.annotation.param.Parameter;
import org.mule.runtime.extension.api.annotation.param.Optional;
import org.mule.runtime.api.connection.ConnectionValidationResult;
import org.mule.runtime.api.connection.PoolingConnectionProvider;
import org.mule.runtime.api.connection.ConnectionProvider;
import org.mule.runtime.api.connection.CachedConnectionProvider;
import org.mule.runtime.extension.api.annotation.param.display.DisplayName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RPLPConnectionProvider implements PoolingConnectionProvider<RPLPConnection> {

  private final Logger LOGGER = LoggerFactory.getLogger(RPLPConnectionProvider.class);

  @Parameter
  @Optional(defaultValue = "localhost")
  private String hostname;

  @Parameter
  @Optional(defaultValue = "RPLPQueue")
  private String queueName;

  @Override
  public RPLPConnection connect() throws ConnectionException {
    return new RPLPConnection(hostname, queueName);
  }

  @Override
  public void disconnect(RPLPConnection connection) {
    try {
      connection.disconnect();
    } catch (Exception e) {
      LOGGER.error("Error while disconnecting [" + connection.getId() + "]: " + e.getMessage(), e);
    }
  }

  @Override
  public ConnectionValidationResult validate(RPLPConnection connection) {
    ConnectionValidationResult result;
    if(connection.isConnected()){
      result = ConnectionValidationResult.success();
    } else {
      result = ConnectionValidationResult.failure("Connection failed " + connection.getId(), new Exception());
    }
    return result;
  }
}

A connection class is used by the connection provider to handle the details of the configuration, in this case a connection to a RabbitMQ.

package com.redpilllinpro.mule.demo;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public final class RPLPConnection {
  private final Logger LOGGER = LoggerFactory.getLogger(RPLPConnection.class);

  private ConnectionFactory factory = new ConnectionFactory();
  private Connection connection;
  private Channel channel;
  private String queueName;

  public void publish(byte[] payload){
    try {
      channel.basicPublish("", queueName, null, payload);
    } catch (IOException e) {
      LOGGER.error("Coop Governance logging failed. " + e.getMessage());
    }
  }

  public RPLPConnection(String hostname, String queueName) {
    factory.setHost(hostname);
    this.queueName = queueName;
    try {
      connection = factory.newConnection();
      channel = connection.createChannel();
    } catch (Exception e) {
      channel = null;
      connection = null;
      LOGGER.error("Coop Governance logging connection failed. " + e.getMessage());
    }
  }

  public String getId() {
    if (connection != null){
      return String.valueOf(connection.hashCode());
    }else {
      return "No connection";
    }
  }

  public void disconnect() {
    try {
      channel.close();
      connection.close();
    } catch (Exception e) {
      LOGGER.warn("Queue disconnect failed. " + e.getMessage());
    } finally {
      channel = null;
      connection = null;
    }
  }

  public boolean isConnected(){
    return channel != null;
  }
}

Finally the operations that the component offers are defined as public methods in an operations class.

package com.redpilllinpro.mule.demo;

import static org.mule.runtime.extension.api.annotation.param.MediaType.ANY;

import org.mule.runtime.extension.api.annotation.param.MediaType;
import org.mule.runtime.extension.api.annotation.param.Config;
import org.mule.runtime.extension.api.annotation.param.Connection;

public class RPLPOperations {

  @MediaType(value = ANY, strict = false)
  public void publish(@Config RPLPConfiguration configuration, @Connection RPLPConnection connection, String message){
    message = configuration.getConfigId() +": "+ message;
   connection.publish(message.getBytes());
  }

}

The Mule flow

To use the new component in a Mule project, add a dependency to the component in the Mule project pom; remember the classifier.

<dependency>
  <groupId>com.redpillinpro.mule.demo</groupId>
  <artifactId>rplp-extension</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <classifier>mule-plugin</classifier>
</dependency>

The new component will appear in the Mule Palette and can be used in flows.

<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:rplp="http://www.mulesoft.org/schema/mule/rplp"
  xmlns="http://www.mulesoft.org/schema/mule/core"
  xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/rplp http://www.mulesoft.org/schema/mule/rplp/current/mule-rplp.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
  <rplp:config name="RPLP_Config" doc:name="RPLP Config" configId="id2">
  </rplp:config>
  <http:listener-config name="HTTP_Listener_config" doc:name="HTTP Listener config">
    <http:listener-connection port="8081" host="0.0.0.0"/>
  </http:listener-config>
  <flow name="rplp-demoFlow">
    <http:listener doc:name="Listener" config-ref="HTTP_Listener_config" path="/rplp"/>
    <rplp:publish doc:name="Publish" config-ref="RPLP_Config" message="#[payload]"/>
  </flow>
</mule>

Morten Furbo Skødt Nielsen

Integration Consultant at Redpill Linpro

Morten Furbo works as an integration specialist and Java developer from our office in Copenhagen, creating integrations in Mule for various customers.

Containerized Development Environment

Do you spend days or weeks setting up your development environment just the way you like it when you get a new computer? Is your home directory a mess of dotfiles and metadata that you’re reluctant to clean up just in case they do something useful? Do you avoid trying new versions of software because of the effort to roll back software and settings if the new version doesn’t work?

Take control over your local development environment with containerization and Dev-Env-as-Code!

... [continue reading]

Ansible-runner

Published on February 27, 2024

Portable Java shell scripts with Java 21

Published on February 21, 2024