Mulesoft Custom Connector Using Mule SDK for Mule 4

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

However, you can develop your own connector using the 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 article 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 providers 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. For more info Mule Training

Go to the directory where you want to 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

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

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. Learn more skills from Mule 4 Training

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 = "bfc3e1a682d19fbebc45954fafd1f3b7";
   private static final String chApixuKey = "576db840a47c478297015039180112";
      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. For more skills Mulesoft Online Training

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);
     }    
}

To get in-depth knowledge, enroll for a live free demo on Mulesoft Training

Leave a comment

Design a site like this with WordPress.com
Get started