Mulesoft custom connector using Mule SDK for Mule 4

 This article will walk you through a process of developing your own Mulesoft 4 Custom Connector using Mule HTTP Client. It will allow you to use Mule 4 SDK.

 Mulesoft 4 Custom Connector, Mulesoft 4 SDK, Mule 4 Custom Connector, Mulesoft Weather Connector, Mule 4 Connector

  Mulesoft    |      Anupam Chakraborty    |      Jan 01 2019 11:16 PM

 Login to Like      Login to Follow      304 Views

Introduction

MuleSoft Anypoint Connectors helps to through various Protocols and APIs. Mulesoft has a range of inbuilt connector connecting to various protocols like SFTP, FTP, JDBC etc or to other SAAS systems like Salesforce and different Google and AWS services, and many more. You can use this connector as they are available at your disposal.

However, you can develop your own connector using new Mule SDK platform for Mule Runtime 4. This is different from the options using Mule runtime 3 where Mule Connector Devkit was needed.

This blog will walk you through a process of developing your own Mule Connector using Mule HTTP Client. This would be a weather connector where you can pass the US ZIP Code and select between 3 weather provider to get the weather for that ZIP Code


Prerequisites

  • Java JDK Version 8
  • Eclipse [I am using Luna]
  • Anypoint Studio 7 [for testing]
  • Apache Maven 3.3.9 or higher


Steps to Create a Connector

One important point to remember before we start is that the Mule SDK runs better on eclipse than on Anypoint Studio. Hence, we would use Eclipse to build our connector but Anypoint Studio to build the Mule app to use this connector

The first step is to generate the app from an archetype. We would use the archetype from mule.org.

Go to the directory where you want you create the connector. This would be your eclipse workspace. Execute the following command to create the basic project structure.

mvn org.mule.extensions:mule-extensions-archetype-maven-plugin:generate

Complete the configuration through the console by answering 5 questions:

Enter the name of the extension: Weather Connector
Enter the extension's groupId: us.anupam.muleConnector
Enter the extension's artifactId: mule-weather-connector
Enter the extension's version: 1.0.0
Enter the extension's main package: org.mule.extension.weather

Now go into the folder where the app is created and run the following to make the code complete for eclipse to understand.

cd mule-weather-connector
mvn eclipse:clean

mvn eclipse:eclipse

Go to Eclipse, File > Open Project from File System, and select the project directory you have created in the last step. Click Finish.

After you open this project in Anypoint studio, you will get a number of classes, annotated with mule SDK annotations as below:


Let us now understand the importance of this classes.

WeatherExtension.java

This class would identify the various properties of your connector. Note that in Mule 4 a connector is nothing but an extension. This class would identify which is the configuration class, which are the Operation classes etc.


WeatherConfiguration.java

This would contain all the information that you want from the global configuration of the Connector.


WeatherConnection.java

The connection class is responsible for handling the connection and in our case, most of the actual coding will be here.


WeatherConnectionProvider.java

This class is used to manage and provide the connection with the target system. The connection provider must implement once of the connection provide available in mule. The options are PoolingConnectionProvider, CachedConnectionProvider and ConnectionProvider. We will use PoolingConnectionProvider.


WeatherOperations.java

This would be the class where you would define all the necessary operations. There can be multiple operation class files.

Now for our ease, we would reorganize the classes to make it more meaningful. After the reorganization, our classes in the package explorer would look like this.

Now let us go through the code in this individual classes.

WeatherExtension.java

package org.mule.extension.webdav.internal;

/**
 * This is the main class of an extension, is the entry point from which configurations, connection providers, operations
 * and sources are going to be declared.
 */
@Xml(prefix = "weather")
@ConnectionProviders(WeatherConnectionProvider.class)
@Extension(name = "Weather", vendor = "anupam.us", category = COMMUNITY)
@Operations({WeatherZipOperations.class})
public class WeatherExtension {

}

Note the annotation Operations, here you can have multiple classes e.g. could have been

@Operations({WeatherZipOperations.class, WeatherCityStateOperations.class })


WeatherConstants.java

package org.mule.extension.webdav.internal;

public class WeatherConstants {

public static final String ZIP = "Get weather by ZIP";
   
   public static final String chYahoo = "Yahoo";
   public static final String chOpenWthr = "OpenWeather";
   public static final String chApixu = "APIXU";
   
   private static final String chOpenWthrKey = "***********************";
   private static final String chApixuKey = "**********************";

      private WeatherConstants() {
            
      }
      
      /**
       * 
       * @param channel
       * @return
       */
      public static String getUrl(String channel) {
            switch (channel) {
     case chYahoo:
           return ("https://query.yahooapis.com/v1/public/yql");
     case chOpenWthr:
           return ("http://api.openweathermap.org/data/2.5/forecast");
     case chApixu :
           return ("http://api.apixu.com/v1/current.json");
     }
            return null;
      }

      /**
       * 
       * @param wChannel
       * @param i
       * @return
       */
      public static MultiMap<String, String> getQueryForZip(String wChannel, int zip) {
            MultiMap<String, String> q = new MultiMap<String, String>();
            
            if(wChannel.equals(chYahoo)) {
                   q.put("q", "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text='" + zip + "')");
                   q.put("format","json");
                   q.put("env", "store://datatables.org/alltableswithkeys");
            }
            
            if(wChannel.equals(chOpenWthr)) {
                   q.put("zip", zip + ",us");
                   q.put("APPID",chOpenWthrKey);
            }
            
            if(wChannel.equals(chApixu)) {
                   q.put("q", Integer.toString(zip));
                   q.put("key", chApixuKey);
            }
            return q;
      }
}

This is a class that I would use to store all constants in one place. Just a good habit.


WeatherGenConfig.java

	package org.mule.connect.internal.connection;
	public class WeatherGenConfig {
	private static final String GENL = "General";
	public enum Channel
     {
        openWeather, yahoo, forecast 
     };

     @Parameter
     @Placement(tab = GENL)
     @DisplayName("Weather Channel")
     @Summary("Options: openweather, yahoo, forecast ")
 @Expression(org.mule.runtime.api.meta.ExpressionSupport.NOT_SUPPORTED)
     private String wChannel;


     public String getWChannel() {
           return wChannel;
     }
}


WeatherConnectionProvider.java


package org.mule.connect.internal.connection;

public class WeatherConnectionProvider implements PoolingConnectionProvider<WeatherConnection> {

 private final Logger LOGGER = LoggerFactory.getLogger(WeatherConnectionProvider.class);
 
 @Parameter
 @Placement(tab = "Advanced")
 @Optional(defaultValue = "5000")
 int connectionTimeout;

 @ParameterGroup(name = "Connection")
 WeatherGenConfig genConfig;

 @Inject
 private HttpService httpService; 
 
 /**
  * 
  */
 @Override
 public WeatherConnection connect() throws ConnectionException {
      return new WeatherConnection(httpService, genConfig, connectionTimeout);
 }
 /**
  * 
  */
 @Override
 public void disconnect(WeatherConnection connection) {
      try {
            connection.invalidate();
      } catch (Exception e) {
            LOGGER.error("Error while disconnecting to Weather Channel " + e.getMessage(), e);
      }
 }
 /**
  * 
  */
 @Override
 public ConnectionValidationResult validate(WeatherConnection connection) {
      ConnectionValidationResult result;
      try {
           if(connection.isConnected()){
                  result = ConnectionValidationResult.success();
            } else {
                  result = ConnectionValidationResult.failure("Connection Failed", new Exception());
            }
     } catch (Exception e) {
           result = ConnectionValidationResult.failure("Connection failed: " + e.getMessage(), new Exception());
     }
   return result;
 }
}

This is very important as we are using the Mule HTTP Client and not Apache HTTP Client. We are injecting the Mule HTTP Client into our connector using the @Inject annotation.


WeatherConnection.java


package org.mule.connect.internal.connection;

/**
 * This class represents an extension connection just as example (there is no real connection with anything here c:).
 */
public class WeatherConnection {

      private WeatherGenConfig genConfig;
      private int connectionTimeout;
      private HttpClient httpClient;
      private HttpRequestBuilder httpRequestBuilder;
      
      /**
       * 
       * @param gConfig
       * @param pConfig
       * @param cTimeout
       */
      public WeatherConnection(HttpService httpService, WeatherGenConfig gConfig, int cTimeout) {
            genConfig = gConfig;
            connectionTimeout = cTimeout;
            
            initHttpClient(httpService);
      }

      /**
       * 
       * @param httpService
       */
      
      public void initHttpClient(HttpService httpService){
            HttpClientConfiguration.Builder builder = new HttpClientConfiguration.Builder();
            builder.setName("AnupamUsWeather");
            httpClient = httpService.getClientFactory().create(builder.build());
            
            httpRequestBuilder = HttpRequest.builder();
            
            httpClient.start();
      }

      /**
       * 
       */
   public void invalidate() {
       httpClient.stop();
   }
   
   public boolean isConnected() throws Exception{
     
     String wChannel = genConfig.getWChannel();
     String strUri = WeatherConstants.getUrl(wChannel);
     MultiMap<String, String> qParams = WeatherConstants.getQueryForZip(wChannel,30328);

            HttpRequest request = httpRequestBuilder
                          .method(Method.GET) 
                          .uri(strUri)
                          .queryParams(qParams)
                          .build();

            HttpResponse httpResponse = httpClient.send(request,connectionTimeout,false,null);
            
            if (httpResponse.getStatusCode() >= 200 && httpResponse.getStatusCode() < 300)
                   return true;
            else
                   throw new ConnectionException("Error connecting to the server: Error Code " + httpResponse.getStatusCode()
                   + "~" + httpResponse);
      }
   
   /**
    * 
    * @param Zip
    * @return
    */
      public InputStream callHttpZIP(int iZip){
            HttpResponse httpResponse = null;
            String strUri = WeatherConstants.getUrl(genConfig.getWChannel());
            System.out.println("URL is: " + strUri);
            MultiMap<String, String> qParams = WeatherConstants.getQueryForZip(genConfig.getWChannel(),iZip);
            
            HttpRequest request = httpRequestBuilder
                          .method(Method.GET) 
                          .uri(strUri)
                          .queryParams(qParams)
                          .build();
            
            System.out.println("Request is: " + request);
            
            try {
                   
                   httpResponse = httpClient.send(request,connectionTimeout,false,null);
                   System.out.println(httpResponse);
                   return httpResponse.getEntity().getContent();
                   
            } catch (IOException e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
            } catch (TimeoutException e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
            } catch (Exception e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
            }
            return null;
      }     
}

And finally


WeatherZipOperations.java

package org.mule.connect.internal.operations;

/**
 * This class is a container for operations, every public method in this class will be taken as an extension operation.
 */
public class WeatherZipOperations {

     @Parameter
     @Example("30303")
     @DisplayName("ZIP Code")
     private int zipCode;
     
     @MediaType(value = ANY, strict = false)
     @DisplayName(WeatherConstants.ZIP)
     public InputStream getWeatherByZip(@Connection WeatherConnection connection){
           return connection.callHttpZIP(zipCode);
     }    
}


We can install this connector into local maven repository using the command:

mvn clean install

or if you want to skip the Junit Tests

mvn clean install -DskipTests

You can use this connector in your Mule 4 application by adding the following dependency in pom.xml:

<dependency>
     <groupId>us.anupam.muleConnector</groupId>
    <artifactId>mule-weather-connector</artifactId>
     <version>1.0.0</version>
     <classifier>mule-plugin</classifier>
</dependency>


Now let us use the connector in the Mulesoft App to connect and get a Weather Details.

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

<mule xmlns:weather="http://www.mulesoft.org/schema/mule/weather" xmlns:http="http://www.mulesoft.org/schema/mule/http"
      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/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/weather http://www.mulesoft.org/schema/mule/weather/current/mule-weather.xsd">
      <http:listener-config name="HTTP_Listener_config" doc:name="HTTP Listener config" doc:id="fe0a0ccb-0c9c-4f0f-a3ff-bbef06b0d209" >
            <http:listener-connection host="0.0.0.0" port="8081" />
      </http:listener-config>
      <weather:config name="Weather_Config" doc:name="Weather Config" doc:id="55ac533e-25e1-43fe-b9c9-bc262f15cfd3" >
            <weather:connection wChannel="Yahoo" />
      </weather:config>
      <flow name="weathertestFlow" doc:id="baf6323b-0ea4-4694-9440-2f1512a5c02f" >
            <http:listener doc:name="Listener" doc:id="e6d130ee-4a5d-400a-88c5-ce0414073823" config-ref="HTTP_Listener_config" path="zip" allowedMethods="GET"/>
            <weather:get-weather-by-zip zipCode="30328" doc:name="Get weather by ZIP" doc:id="f044ecd9-2048-4bbc-9cd2-cdb932b4f496" config-ref="Weather_Config"/>
      </flow>
</mule>


Try this out and run and let me know in the comments section if this is useful and you can get this to work or if you want me to change anything!

Please find the code in GitHub.

Thanks.


Comments:

Leave a Comment:

Please login to post comment into this page.

Please login to submit a new Blog.