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.

Just-Make-toolbox

make is a utility for automating builds. You specify the source and the build file and make will determine which file(s) have to be re-built. Using this functionality in make as an all-round tool for command running as well, is considered common practice. Yes, you could write Shell scripts for this instead and they would be probably equally good. But using make has its own charm (and gets you karma points).

Even this ... [continue reading]

Containerized Development Environment

Published on February 28, 2024

Ansible-runner

Published on February 27, 2024