Статьи

Mulesoft Custom Connector с использованием Mule SDK для Mule 4

Вступление

Anypoint Connectors MuleSoft помогают через различные протоколы и API. Mulesoft имеет ряд встроенных разъемов, соединяющихся с различными протоколами, такими как SFTP, FTP, JDBC и т. Д., Или с другими системами SAAS, такими как Salesforce, с различными сервисами Google и AWS, а также со многими другими. Вы можете использовать этот разъем, так как они доступны в вашем распоряжении.

Однако вы можете разработать свой собственный коннектор, используя новую платформу Mule SDK для Mule Runtime 4. Это отличается от опций, использующих Mule Runtime 3, где был необходим Mule Connector Devkit.

Эта статья проведет вас через процесс разработки вашего собственного Mule Connector, используя Mule HTTP Client. Это будет погодный соединитель, в котором вы можете передать почтовый индекс США и выбрать одного из 3 поставщиков погоды, чтобы получить погоду для этого почтового индекса.

Предпосылки

  • Java JDK версия 8
  • Затмение [я использую Луну]
  • Anypoint Studio 7 [для тестирования]
  • Apache Maven 3.3.9 или выше

Шаги по созданию соединителя

Перед запуском важно помнить, что Mule SDK работает лучше при затмении, чем в Anypoint Studio. Следовательно, мы будем использовать Eclipse для создания нашего соединителя, а Anypoint Studio — для создания приложения Mule для использования этого соединителя.

Первым шагом является создание приложения из архетипа. Мы будем использовать архетип из mule.org.

Перейдите в каталог, где вы хотите создать соединитель. Это будет ваше рабочее пространство затмения. Выполните следующую команду, чтобы создать базовую структуру проекта.

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

Завершите настройку через консоль, ответив на 5 вопросов:

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

Теперь перейдите в папку, в которой создано приложение, и выполните следующую команду, чтобы сделать код завершенным для понимания Eclipse.

cd mule-weather-connector
mvn eclipse:clean

mvn eclipse:eclipse

Перейдите в Anypoint studio, выберите «Файл»> «Открыть проект из файловой системы» и выберите каталог проекта, созданный на последнем шаге. Нажмите Готово.

После того, как вы откроете этот проект в Anypoint studio, вы получите ряд классов с аннотациями Mule SDK, как показано ниже:

Давайте теперь поймем важность этих занятий.

WeatherExtension.java

Этот класс будет определять различные свойства вашего соединителя. Обратите внимание, что в Mule 4 разъем — это не что иное, как расширение. Этот класс будет определять, какой класс конфигурации, какие классы операций и т. Д.

WeatherConfiguration.java

Это будет содержать всю информацию, которую вы хотите от глобальной конфигурации соединителя.

WeatherConnection.java

Класс соединения отвечает за обработку соединения, и в нашем случае большая часть фактического кодирования будет здесь.

WeatherConnectionProvider.java

Этот класс используется для управления и обеспечения связи с целевой системой. Поставщик соединения должен реализовать, как только соединение будет доступно в муле. Возможные варианты: PoolingConnectionProvider, CachedConnectionProvider и ConnectionProvider. Мы будем использовать PoolingConnectionProvider.

WeatherOperations.java

Это будет класс, в котором вы будете определять все необходимые операции. Может быть несколько файлов классов операций.

Теперь для нашей простоты мы реорганизовали бы классы, чтобы сделать их более значимыми. После реорганизации наши классы в проводнике пакетов будут выглядеть следующим образом.

Теперь давайте пройдемся по коду в этих отдельных классах.

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 {

}

Обратите внимание на аннотацию Операции, здесь вы можете иметь несколько классов, например, могли быть

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

Это класс, который я бы использовал для хранения всех констант в одном месте. Просто хорошая привычка.

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

Это очень важно, так как мы используем Mule HTTP Client, а не Apache HTTP Client. Мы вводим HTTP-клиент Mule в наш соединитель, используя аннотацию @Inject.

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

И наконец:

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.