Mulesoft Custom Connector Using Mule SDK for Mule 4
This article will walk you through a process of developing your own Mule Connector using Mule HTTP Client.
Join the DZone community and get the full member experience.
Join For FreeIntroduction
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.
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
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 Anypoint studio, 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's now understand the importance of these 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 these 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 = "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.
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 if you can get this to work or if you want me to change anything! Thanks.
I have updated the blog in https://www.wedointegration.com/post.php?posturl=mulesoft_custom_connector_using_mule_sdk_for_mule_4 with the GitHub Project for ease of learning. Have a look and thanks.
Published at DZone with permission of Anupam Chakraborty. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments