Nav
You are viewing an older version of this section. Click here to navigate to the latest version.

Anypoint Connector DevKit Tutorial

Tutorial Goals

Learn how to create a connector to a web service using the Anypoint Connector DevKit.

DevKit helps you build Anypoint Connectors. This tutorial shows you how to code a connector to support authentication, connections, service operations and more. This is functionality that you as the connector developer expose to the end user of the connector, who is the Mule application developer.

What is the Anypoint Connector DevKit?

The Anypoint Connector DevKit is an essential development tool in the Anypoint Platform that makes coding Anypoint Connectors easier. DevKit is a Maven-based tool that lets you build reusable components that can be run as part of a Mule application, and can be easily configured from Anypoint Studio.

DevKit exposes connector developers to Java annotations that generate code and files to interact with Mule runtime, as well as Anypoint Studio. The generated code provides the interface between the connector and Mule that would otherwise require each connector developer to include extensive boilerplate code, as well as the code and files required to interact with your connector in Anypoint Studio.

What is a Connector?

An Anypoint Connector is a reusable component that interacts with Mule runtime and Anypoint Studio. A connector communicates with a target resource and conveys information between a resource and Mule, and transforms the data into a Mule message.

Anypoint Connector DevKit abstracts the communication that happens between Mule and the external service or API, and generates a user interface to help simplify usage of the connector by the developer who would eventually use it in their application.

A well-developed connector makes Mule app development much simpler for users when handling tasks like pagination, session expirations, and input and output metadata. This tutorial shows you how to create a well-designed connector.

Why Build a Connector Using DevKit?

Here is why you might want to build your own connector:

  • You need to consume an API from a Mule application and you want to make sure your developers maintain consistency by employing the same connector.

  • You have an API and want to add strategic value to your business by providing developers with a connector that provides an interface into your web service.

  • You want to facilitate integration with SaaS and on-premises Web services, applications, and data sources.

  • The API you consume supports Pagination, Batch, and/or has a SQL capability.

  • The API you consume has different entity types and/or its structure changes depending on API/service operation.

  • You want to extend Mule core.

Prerequisites

Before developing a connector you should have a working knowledge of Mule, Anypoint Studio, and Java development in general, specifically the use of Java annotations.

For information on software setup and DevKit, see Setting Up Your Development Environment and Anypoint Connector DevKit.

You can develop a connector on a Windows, Mac, or Linux machine.

Get the Cookbook

Get the cookbook tutorial, which is stored on GitHub:

  1. Clone the https://github.com/mulesoft/mule-cookbook repository into your local machine. For example, to clone to your desired development folder, run:

    
                
             
    1
    2
    
    cd ~/dev
    git clone https://github.com/mulesoft/mule-cookbook.git
  2. Enter your newly created mule-cookbook directory and run the following Maven command:

    
                
             
    1
    
    mvn install eclipse:eclipse
  3. When done, open Anypoint Studio, import the mule-cookbook directory as an existing project. File > Import > Existing Projects into Workspace.

    import existing - mule-cookbook

This should create the several folders you see on the above screen in your workspace in Studio, which is visible in the Package Explorer.

Cookbook Description

Before we move on, let us briefly mention that the Cookbook service is a web service that helps users organize ingredients and recipes they might like to access, store or update from the web.

Cookbook API Description

This API allows users to use the create, read, update, and delete (CRUD) operations for single and multiple recipes and ingredients. The API also allows you to view recently added recipes.

The API is exposed as a:

  • SOAP Service using a WSDL file

  • REST API with a RAML file, and

  • a Java SDK.

Authentication

This connector supports the following authentication mechanisms:

  • Custom authentication with a username and password that provides a token to send with each request as part of the request.

  • OAuthV2

Credentials

Custom Authentication:
Username Password

admin

admin

OAuth:
Client ID Client Secret

ePU9CxyEVFIXF9nMoGH16s1lUGe5JYPPzClnnVBG

YeKAsQCdr264tNmDdvTdJUAh9TQraJqZpwbEoTuz

Connecting to the Service

The web service is available online.

  • To consume the SOAP version and the SDK just make the request to the address: http://devkit-cookbook.cloudhub.io/soap.

  • The rest base url is http://devkit-cookbook.cloudhub.io/rest

    • The OAuth Authorize url is /oauth/authorize

    • The Access Token url is /oauth/accessToken

You can also run it locally since we provide the source code for the servers already ready to start.

The local SOAP Server can be run from the soap-server project by simply executing the com.cookbook.tutorial.service.MuleStoreServer class.

By default it starts the server at address http://localhost:9090/cook-book

The local REST Server can be run from the rest-server project by simply executing the com.cookbook.tutorial.Main class.

By default it starts the server at address http://localhost/9091

Steps to Build the Cookbook Connector

To build a basic connector for the Cookbook service, we need to perform the following steps:

  1. Create a Connector Project.

  2. Add the dependency that contains the client we will use to connect to the service.

  3. Add a configurable URL so that users can specify the URL where the service is hosted.

  4. Add an operation that users can consume in Anypoint Studio.

Create the Cookbook Connector

With the mule-cookbook directory imported as an existing project into your workspace as instructed above, you can begin to create the connector project and start coding.

  1. In Anypoint Studio, click File > New > Anypoint Connector Project or right-click your project name in Package Explorer and click New > Anypoint Connector Project:

    new connector 1
  2. Select the project type you want to create. In this case, select SDK Based:

    new0
  3. Specify the name of your connector and don’t forget to uncheck the default generation before clicking Finish:

    new1

    This generates a project containing the structure and all required elements including a skeleton connector, icons, sample docs file, but no basic tests for your connector.

    new connector 3
You can enable the DevKit view by clicking from the top bar Window > Show View > Other, and look for DevKit in the MuleSoft dropdown.
enable view
Figure 1. DevKit View shows a compact view of the connector.

Connection Configuration

When consuming a service, you may need to configure different values to establish a connection.

To facilitate developing connection configuration strategies, DevKit provides a pair of annotations that let you modularize these definitions inside several classes:

  • The behavior you expose to users, using the @Connector annotation.

  • Code related to the connection configuration is injected where you add the @Config annotation. There are several types of configuration you can use as we will see in this tutorial.

When you mark a class with @Config, DevKit ensures that you have an initialized object set when the Mule app runs and that requests are made to your connector.

Add Dependencies in pom.xml

Let’s start coding by creating a connector that allows you to get the recently added items from the Cookbook service.

Since we don’t need any kind of authentication to consume the recently added recipes, this is the best operation we can use to start learning how to build our connector.

  1. Add our client dependency so that we can use it in our connector. This way we can use the Java API to connect to the Cookbook.

    In your pom.xml file add:

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    
    <dependencies>
      <dependency>
        <groupId>org.mule.modules</groupId>
        <artifactId>sdk-client</artifactId>
        <version>1.0.0-SNAPSHOT</version>
      </dependency>
    </dependencies>

Adding Code to Support Configurable Fields

In the ConnectorConfig.java file add the @Configurable annotation for the address where the Cookbook service is hosted:

  1. Type conf at the editor of your connector config and use control + space to display the templates.

    config field
  2. Define a configurable field inside the @Configuration-annotated class with a default value for the endpoint our client connects to, for example, @Default("http://devkit-cookbook.cloudhub.io/soap")

  3. Any field marked with the @Configurable annotation must have a getter and a setter for that field.

    See full Config source code.

Initialize the Client

In your @Connector-annotated class in the Connector.java file use the @Start annotation on a method to initialize the client.


          
       
1
2
3
4
5
6
        private MuleCookBookClient client;

        @Start
        public void initialize() {
                client = new MuleCookBookClient(config.getAddress());
        }

Anypoint Connectors are made to be fully aware of Mule runtime’s lifecycle without implementing any Mule-specific interface.

There is an annotation method for each of the four phases of the lifecycle.

When a method is annotated with one of these annotations, DevKit invokes it during the lifecycle phase that the annotation represents.

Adding @Processor Methods

  1. Remove the dummy @Processor operation from the Connector.java file.

  2. To add a processor simply type proc in the Studio code editor and use ctrl + space to display the templates and pick the simple processor.

    processor1
  3. Change it to reflect the getRecentlyAdded method signature (see example implementation here), and at this point you have the code in place to build your first connector, which should then be ready for testing.

  4. Run the Generate Sources Action.

    See full Connector source code.

Resolve any package import errors by right-clicking your connector project in the Package Explorer of Studio. Build Path > Add External Archives. Add the applicable .jar files, for example, so your project can reference the cookbook sdk-client. This should stop any import errors on: import com.cookbook.tutorial.client.MuleCookBookClient; import com.cookbook.tutorial.service.Recipe;

You can check references in the properties for your project.

properties for cookbook connector project

At this point you can install this Connector and try it in Studio if you want.

As you modify your connector, you may start seeing error markers on the generated folder.

Just ignore them.

Once you regenerate the sources, the errors will go away as the generated code will be refreshed.

Defining Example Code for Connector Processors

Connectors built with DevKit 3.8 support specification of examples in a different format. See the new specification and generation format at Connector Reference Documentation.

Follow along to add an example use for an operation (annotated with @Processor).

Create the file referenced in the code block in the Connector.java file following {@sample …​} if the file does not exist already.

/**
 * Returns the list of recently added recipes
 *
 * {@sample.xml ../../../doc/cookbook-connector.xml.sample
 * cook-book:getRecentlyAdded}
 *
 * @return A list of the recently added recipes
 */
When you add an example, use the same name as the one that specifies the example in the file. Inside of it you have to put an example of the @Processor.

If you have the "Javadoc check" enabled, the DevKit plugin marks the missing example as an error and provides a quick fix for us to easily add the example.

Otherwise, open the file and type < at the editor and use control + space to display the templates and pick the one that best suits our operation.

sample1

Our example in this case looks like this:


           
        
1
2
3
<!-- BEGIN_INCLUDE(cook-book:getRecentlyAdded) -->
        <cook-book:get-recently-added/>
<!-- END_INCLUDE(cook-book:getRecentlyAdded) -->

Using the Connector in a Mule App

Make a simple app that listens at an HTTP endpoint; when the endpoint is hit, the app retrieves the list of recently added items from the cookbook service using the connector.

  1. Create a Mule application in Studio and add an HTTP listener and specify /get-recently for the path. If this is your first Mule app, take a look at our Hello World Application.

  2. Drop the connector onto the canvas and configure using admin and admin as the credentials (this is for non-OAuth configuration).

    devkit tutorial 4cb84
  3. Drag and drop an Object to JSON transformer after the connector, and run the app. (Play icon or Run As > Mule Application)

    devkit tutorial 1c0f8
    
                 
              
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <?xml version="1.0" encoding="UTF-8"?>
    <mule xmlns:cookbook="http://www.mulesoft.org/schema/mule/cookbook" xmlns:json="http://www.mulesoft.org/schema/mule/json" 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:spring="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
    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/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
    http://www.mulesoft.org/schema/mule/cookbook http://www.mulesoft.org/schema/mule/cookbook/current/mule-cookbook.xsd">
        <http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration"/>
        <cookbook:config name="Cookbook__Configuration" doc:name="Cookbook: Configuration"/>
        <flow name="mule-appFlow">
            <http:listener config-ref="HTTP_Listener_Configuration" path="/get-recently" doc:name="HTTP"/>
            <cookbook:get-recently-added config-ref="Cookbook__Configuration" doc:name="Cookbook"/>
            <json:object-to-json-transformer doc:name="Object to JSON"/>
        </flow>
    </mule>

    If you hit the url http://localhost:8081/get-recently you will see a reply similar to:

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    [
       {
          "created":1428678371866,
          "id":2,
          "lastModified":null,
          "name":"Baked Apples",
          "cookTime":20.0,
          "directions":[
             "Cut the Apples",
             "Put them in the oven",
             "Remove from the oven after 20.0 minutes"
          ],
          "ingredients":[
             {
                "created":1428678371866,
                "id":1,
                "lastModified":null,
                "name":"Apple",
                "quantity":0.0,
                "unit":"UNIT"
             }
          ],
          "prepTime":30.0
       }
    ]

To learn how to create tests for your connector see:

Adding Connection Management

The getRecentlyAdded call doesn’t require authentication — all other Cookbook operations require that you set a token on each request.

The client you are using provides a login call that initializes the token and uses it in subsequent requests.

Take into account that the session can expire and cause the connector to make a login request again.

DevKit provides a set of annotations to keep your code clean and handle the connection.

  1. In the Config.java file, change @Configuration to @ConnectionManagement

    
                 
              
    1
    
    @ConnectionManagement(friendlyName = "Configuration")
  2. Make sure you have equipped the MuleCookbookClient in the ConnectorConfig.java with a getter and a setter.

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
        private MuleCookBookClient client;
    
        public MuleCookBookClient getClient() {
            return client;
        }
    
        public void setClient(MuleCookBookClient client) {
            this.client = client;
        }
    
  3. Implement these four methods as shown:

    • @Connect - Initialize the client, and if the login does not succeed, throw an exception.

      
                      
                   
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
          @Connect
          @TestConnectivity
          public void connect(@ConnectionKey String username, @Password String password) throws ConnectionException {
              setClient(new MuleCookBookClient(getAddress()));
              try {
                  getClient().login(username, password);
              } catch (InvalidCredentialsException e) {
                  throw new ConnectionException(ConnectionExceptionCode.INCORRECT_CREDENTIALS, e.getMessage(), "Invalid credentials");
              }
          }
    • @Disconnect - Release a connection.

      
                      
                   
      1
      2
      3
      4
      
          @Disconnect
          public void disconnect() {
              setClient(null);
          }
    • @ValidateConnection - Check if your connection is alive.

      
                      
                   
      1
      2
      3
      4
      
          @ValidateConnection
          public boolean isConnected() {
              return getClient() != null;
          }
    • @ConnectionIdentifier - Return a string value. This will only be used when debugging.

      
                      
                   
      1
      2
      3
      4
      
          @ConnectionIdentifier
          public String connectionId() {
              return "001";
          }
  4. Remove the declaration of MuleCookBookClient from CookbookConnector.java and remove the method where a new MuleCookBookClient client is instantiated.

    
                 
              
    1
    2
    3
    4
    5
    6
    
            private MuleCookBookClient client;
    
            @Start
            public void initialize() {
                    client = new MuleCookBookClient(config.getAddress());
            }
  5. In our getRecentlyAdded method (annotated as a @Processor) call the getclient method, which should be defined in ConnectorConfig.java.

    
                 
              
    1
    2
    3
    4
    
        @Processor
        public List<Recipe> getRecentlyAdded() {
            return config.getClient().getRecentlyAdded();
        }

See:

If you install this version and try to run the Mule app we created earlier, you will see that it fails with a "SAXParseException" because we need to add a username and password to our configuration.

Just open the global configuration of your connector and check that there are two new fields, username and password. Configure them and you can run the app again.

connection management

This is the updated Mule app XML:


          
       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:cookbook="http://www.mulesoft.org/schema/mule/cookbook" xmlns:json="http://www.mulesoft.org/schema/mule/json" 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:spring="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
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/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
http://www.mulesoft.org/schema/mule/cookbook http://www.mulesoft.org/schema/mule/cookbook/current/mule-cookbook.xsd">
    <http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration"/>
    <cookbook:config name="Cookbook__Configuration" doc:name="Cookbook: Configuration type config" password="admin" username="admin"/>
    <flow name="mule-appFlow">
        <http:listener config-ref="HTTP_Listener_Configuration" path="/get-recently" doc:name="HTTP"/>
        <cookbook:get-recently-added config-ref="Cookbook__Configuration" doc:name="Cookbook"/>
        <json:object-to-json-transformer doc:name="Object to JSON"/>
    </flow>
</mule>

Improving Exception and Error Handling

This section explains how to improve exception handling and reconnecting.

Exception Handling with @Handler

The @Handler feature is useful for avoiding duplicate code for handling exceptions, and makes your code easier to read.

When handling messages retrieved from the API, if you see the message is uninformative and you know how to improve it, use the @Handler mechanism to enrich the error information provided to the user.

To see how it works, let’s create a handler in the connector code for the InvalidEntityException thrown by the Cookbook SDK create() call:

This create() call from MuleCookBookClient.java throws InvalidEntityException

          
       
1
2
3
4
5
6
7
8
9
10
11
12
@Override
   public CookBookEntity create(CookBookEntity entity) throws InvalidEntityException,
           SessionExpiredException {
       Create request = factory.createCreate();
       request.setEntity(entity);
       try {
           return port.create(request, token).getReturn();
       } catch (InvalidTokenException e) {
           logger.warn("Should never happen.", e);
           throw new RuntimeException(e);
       }
     }
  1. Create a new Anypoint Connector Component by right-clicking and navigating to that item in the menu, or via New > Other, selecting this option in the wizard.

    new component
  2. Select the package, component type and class name and click on Finish.

    new component 2
  3. Improve the error message when InvalidEntityException is thrown.

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
        @Handle
        public void handleException(Exception ex) throws Exception {
            if (ex instanceof InvalidEntityException) {
                throw new RuntimeException("You cannot provide an Id when creating an Ingredient");
            } else {
                // Just let it go
                throw ex;
            }
        }

    Check the full source code.

  4. Add a new processor to create Ingredients, noticing how it references the exception handler we included.

    
                 
              
    1
    2
    3
    4
    5
    
        @Processor
        @OnException(handler = CookbookHandler.class)
        public Ingredient createIngredient(@Default("#[payload]") Ingredient Ingredient) throws InvalidEntityException, SessionExpiredException {
            return (Ingredient) config.getClient().create(Ingredient);
        }

    Check the full source code.

Handling Session Expiration

There is no need for users to add custom code in their Mule apps to handle session expiration. DevKit provides a mechanism to do this cleanly.

Just annotate your @Processor method with the @ReconnectOn exception.


          
       
1
2
3
4
5
6
    @Processor
    @OnException(handler = CookbookHandler.class)
    @ReconnectOn(exceptions = { SessionExpiredException.class })
    public Ingredient createIngredient(@Default("#[payload]") Ingredient Ingredient) throws InvalidEntityException, SessionExpiredException {
        return (Ingredient) config.getClient().create(Ingredient);
    }

Check the full source code.

In our Mule app, you can configure a reconnection strategy so that our Mule app is ready to handle session expirations.

Global Configuration for the Cookbook connector dialog window:

reconnect

The generated XML for the Cookbook connector configuration element looks like:


          
       
1
2
3
<cookbook:config-type doc:name="config" name="config" password="admin" username="admin">
    <reconnect/>
</cookbook:config-type>

Adding DataSense

What is it?

DataSense improves the user experience at design time when creating Mule applications by enabling your connector to determine the API of your target resource.

Even though DataSense is optional, its use enables connector users to acquire metadata of the entities in a service.

See:

In this tutorial, we use a static DataSense model, which means that the entities are fixed and known upfront, and do not change. The fields that this model supports are also fixed.

Using DataSense

To use DataSense in the cookbook:

  1. Analyze the entities in {mule} service. We only have two simple entities, Recipe and Ingredient that both extend from CookBookEntity.

  2. Look at how our createIngredient operation looks inside Anypoint Studio, and how it interacts with other components.

    
                 
              
    1
    2
    3
    4
    5
    6
    
        @Processor
        @OnException(handler = CookbookHandler.class)
        @ReconnectOn(exceptions = { SessionExpiredException.class })
        public Ingredient createIngredient(@Default("#[payload]") Ingredient Ingredient) throws InvalidEntityException, SessionExpiredException {
            return (Ingredient) config.getClient().create(Ingredient);
        }

Handling Ingredients

We defined the operation so that it receives an Ingredient, and it returns an Ingredient with the extra fields populated from the server.

Let’s handle the ingredients:

  1. Check the input metadata to see that the expected output is a POJO, with the expected fields our Ingredient has:

    datasense expected ingredients
  2. Verify that the output metadata is expected:

    datasense ingredients
  3. Drag and drop a Transform Message element either behind or after our connector. The input/output structure is collected automatically:

    datasense input
    Figure 2. Transform Message Receiving Connector Output

    Because DevKit auto-generates static metadata, DevKit automatically ensures that your connector knows how to propagate metadata information.

Handling Recipes

We don’t have just Ingredients, we also have Recipes, so we don’t want one method for each entity we have in our model.

Modify your connector to just work with the CookBookEntity class:

  1. Create an operation:

    
                  
               
    1
    2
    3
    4
    5
    6
    
            @Processor
            @OnException (handler=CookbookHandler.class)
            @ReconnectOn(exceptions = { SessionExpiredException.class })
            public CookBookEntity create(@Default("#[payload]") @RefOnly CookBookEntity entity) throws InvalidEntityException, SessionExpiredException {
                    return config.getClient().create(entity);
            }
  2. The @RefOnly annotation is used to tell DevKit that the input can only be specified as a reference (due to a DevKit limitations on handling Abstract classes).

    Let’s see how this affects the UI and user experience.

    Studio, can no longer determine input or output type:

    ref only input
    Figure 3. Connector Input with @RefOnly
    ref only output
    Figure 4. Connector Output with Abstract Class

In the next section we get back our DataSense-friendly user experience.

The full source code of our connector with a create, update, get and delete operation is available here.

Implementing a MetaDataCategory

To implement DataSense using a @MetaDataCategory, you need to separate your implementation in two steps, retrieving the keys and describing the keys.

  1. Use the Anypoint DevKit Component wizard to create a new MetaDataCategory

    new metadatacategory
  2. Modify the method annotate with @MetaDataKeyRetriever to retrieve two keys, one for Recipe and another for Ingredient.

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
        @MetaDataKeyRetriever
        public List<MetaDataKey> getMetaDataKeys() throws Exception {
            List<MetaDataKey> keys = new ArrayList<MetaDataKey>();
    
            // Generate the keys
            keys.add(new DefaultMetaDataKey("id1", "Ingredient"));
            keys.add(new DefaultMetaDataKey("id2", "Recipe"));
    
            return keys;
        }
  3. Modify the method annotate with @MetaDataRetriever, to retrieve the description. Because we use a static model, we can just create a POJO model:

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
        @MetaDataRetriever
        public MetaData getMetaData(MetaDataKey key) throws Exception {
            DefaultMetaDataBuilder builder = new DefaultMetaDataBuilder();
            // Since our model is static and we can simply create the pojo model.
            PojoMetaDataBuilder<?> pojoObject = null;
            if ("id1".equals(key.getId())) {
                pojoObject = builder.createPojo(Ingredient.class);
            } else if ("id2".equals(key.getId())) {
                pojoObject = builder.createPojo(Recipe.class);
            } else {
                throw new RuntimeException("Invalid key:" + key.getId());
            }
            MetaDataModel model = pojoObject.build();
            MetaData metaData = new DefaultMetaData(model);
    
            return metaData;
        }
    

    Check the full source code.

To use this in our connector, modify our @Processors so that a user can pick the entities.

  1. Annotate our @Connector class with the @MetaDataScope annotation. This sets the default MetaDataCategory that’s used every time users choose a @Processor that has a @MetaDataKeyParam.

    
                 
              
    1
    2
    3
    
    @Connector(name = "cookbook", friendlyName = "Cookbook")
    @MetaDataScope(DataSenseResolver.class)
    public class CookbookConnector {
  2. To describe input and output, add a String annotated with @MetaDataKeyParam, and specify that it affects input and output by adding the affects=MetaDataKeyParamAffectsType.BOTH :

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    
        @Processor
        @OnException(handler = CookbookHandler.class)
        @ReconnectOn(exceptions = { SessionExpiredException.class })
        public CookBookEntity create(@MetaDataKeyParam(affects = MetaDataKeyParamAffectsType.BOTH) String type, @Default("#[payload]") @RefOnly CookBookEntity entity)
                throws InvalidEntityException, SessionExpiredException {
            return config.getClient().create(entity);
        }
  3. In your get operation you need specify that the affect only applies to the output so we modify it just a little:

    
                 
              
    1
    2
    3
    4
    5
    6
    7
    
        @Processor
        @OnException(handler = CookbookHandler.class)
        @ReconnectOn(exceptions = { SessionExpiredException.class })
        public CookBookEntity get(@MetaDataKeyParam(affects = MetaDataKeyParamAffectsType.OUTPUT) String type, @Default("1") Integer id) throws InvalidEntityException,
                SessionExpiredException, NoSuchEntityException {
            return config.getClient().get(id);
        }
  4. Check our new connector looks in Studio. We have a combo after we select the entity type and save it. This automatically refreshes our metadata.

    datasense static

    Now even the {dataWeave} knows how to interact with our @Connector:

    datasense static2

View connector full source code here.

Dynamic DataSense

In the previous section we covered the scenario when your model is static. Let’s take a look into a much more complex scenario.

There are APIs that provide a way to get entity definitions dynamically. Salesforce, NetSuite are just some examples of these.

In our case, our Cookbook provides an operation that describes our entities, so let’s use that instead to get the entities and structure:

  1. Get the supported entities, and generate a key that we can use later:

    
                  
               
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
        @MetaDataKeyRetriever
        public List<MetaDataKey> getMetaDataKeys() throws Exception {
            List<MetaDataKey> keys = new ArrayList<MetaDataKey>();
            List<CookBookEntity> entities = getConnector().getConfig()
                    .getClient().getEntities();
            // Generate the keys
            for (CookBookEntity entity : entities) {
                keys.add(new DefaultMetaDataKey(entity.getClass().getName() + "#"
                        + entity.getId(), entity.getName()));
            }
    
            return keys;
        }
  2. Use a structure that we can dynamically modify, and the way to do this in Mule is by using a Map<String,Object> as parameters and/or return types of our connector.

    Mule provides a builder that helps us generate the MetaData for the entities.

    
                  
               
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
        @MetaDataRetriever
        public MetaData getMetaData(MetaDataKey key) throws Exception {
            DefaultMetaDataBuilder builder = new DefaultMetaDataBuilder();
            // Since our model is static and we can simply create the pojo model.
            String[] keyParts = key.getId().split("#");
            if (keyParts.length != 2) {
                throw new RuntimeException(
                        "Invalid key. Format should be 'entityType#id'");
            }
            Integer id = Integer.valueOf(keyParts[1]);
            CookBookEntity entity = (CookBookEntity) Class.forName(keyParts[0])
                    .newInstance();
            entity.setId(id);
            Description description = getConnector().getConfig().getClient()
                    .describeEntity(entity);
    
            DynamicObjectBuilder<?> dynamicObject = builder.createDynamicObject(key
                    .getId());
    
            for (Description fields : description.getInnerFields()) {
                addFields(fields, dynamicObject);
            }
    
            MetaDataModel model = builder.build();
            MetaData metaData = new DefaultMetaData(model);
    
            return metaData;
        }
  3. Check the full source code.

  4. In our @Connector now we need to add the code to generate the entities from the Map, and return Map in all our operations. Why is this important? To maintain consistency in our API.

    This is how our new Create looks:

    
                  
               
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
        @Processor
        @OnException(handler = CookbookHandler.class)
        @ReconnectOn(exceptions = { SessionExpiredException.class })
        public Map<String, Object> create(@MetaDataKeyParam(affects = MetaDataKeyParamAffectsType.BOTH) String type, @Default("#[payload]") @RefOnly Map<String, Object> entity)
                throws InvalidEntityException, SessionExpiredException {
            ObjectMapper m = new ObjectMapper();
            CookBookEntity input = null;
            if (type.contains("com.cookbook.tutorial.service.Recipe")) {
                input = m.convertValue(entity, Recipe.class);
            } else if (type.contains("com.cookbook.tutorial.service.Ingredient")) {
                input = m.convertValue(entity, Ingredient.class);
            } else {
                throw new InvalidEntityException("Don't know how to handle type:" + type);
            }
            return m.convertValue(this.getConfig().getClient().create(input), Map.class);
        }

    Note that now the UI is a different form before, because the input is now a map.

    datasense ui
  5. In our Mule app, we cannot update the metadata and use our connector. And, as you can see, note that we have a map structure instead of a POJO:

    datasense map

To see how to test DataSense check our Testing DataSense guide.

Adding Pagination

Implement Pagination

In order to show this feature you are going to add a processor that will call the searchWithQuery operation of the SDK.

You need to do 3 things in order to have a paginated operation:


          
       
1
2
3
4
5
6
7
8
    @Processor
    @ReconnectOn(exceptions = { SessionExpiredException.class })
    @Paged (1)
    public ProviderAwarePagingDelegate<Map<String, Object>, CookbookConnector> queryPaginated( (2)
            final String query, final PagingConfiguration pagingConfiguration) (3)
            throws SessionExpiredException {
        return new CookbookPagingDelegate(query, pagingConfiguration.getFetchSize());
    }
1 Annotated your processor with @Paged.
2 Return a ProviderAwarePagingDelegate
3 Receive as one of your parameters a PagingConfiguration

When implementing your ProviderAwarePagingDelegate you need to specify two elements:

  1. The type of the list you will return in each page. In your case a Map<String,Object>

  2. The type of the Connector.

To create it use the Anypoint DevKit Component wizard and:

  1. Specify the package were you want to create it. In this example org.mule.cookbook.pagination

  2. Specify you want to create a ProviderAwarePagingDelegate.

  3. Set the class name as CookbookPagingDelegate

    pagination component

After that you just need to implement the methods required for you to handle the new page request.

You can find the full source code of the CookbookPagingDelegate.

If you have a reconnection strategy, DevKit will automatically reconnect and retry to get a page. It is important that you handle the state of your PagingDelegate to make sure you retry the last page that was not retrieved.

See:

Using Pagination In Your Mule App

You can for example use a foreach in front of your paged processor:

pagination example

    
             
          
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
&lt;?xml version="1.0" encoding="UTF-8"?&gt;

&lt;mule xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns:cookbook="http://www.mulesoft.org/schema/mule/cookbook" 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:spring="http://www.springframework.org/schema/beans" version="EE-3.6.1"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
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/cookbook http://www.mulesoft.org/schema/mule/cookbook/current/mule-cookbook.xsd
http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd"&gt;
    &lt;cookbook:config name="CookBook__Connection_Management_Config" username="admin" password="admin" doc:name="CookBook: Connection Management Config"&gt;
        &lt;reconnect/&gt;
    &lt;/cookbook:config&gt;
    &lt;http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration"/&gt;
    &lt;flow name="mule-appFlow1"&gt;
        &lt;http:listener config-ref="HTTP_Listener_Configuration" path="/page" doc:name="HTTP"/&gt;
        &lt;cookbook:query-paginated config-ref="CookBook__Connection_Management_Config" query="GET ALL FROM INGREDIENT" fetchSize="5" doc:name="CookBook"/&gt;
        &lt;foreach doc:name="For Each"&gt;
            &lt;logger message="#[payload]" level="INFO" doc:name="Logger"/&gt;
        &lt;/foreach&gt;
        &lt;set-payload value="Finished Processing" doc:name="Set Payload"/&gt;
    &lt;/flow&gt;
&lt;/mule&gt;

Installing a Connector

Using the Anypoint DevKit plugin

Installing the connector is basically the same as installing any Eclipse plugin.

To install your connector from the DevKit plugin:

  1. Right-click your project name in Package Explorer, and click Anypoint Connector > Install Or Update.

    install1

    This triggers the DevKit Maven build tool, which generates an update site folder.

    The files will be copied under the dropins folder located in the same directory as the AnypointStudio executable, and installed without the need of rebooting your AnypointStudio.

  2. You can now use your connector in a Mule app.

    install2

To uninstall the connectors you can either use the shortcut from the UI or remove the folder from the dropins directory and reboot your AnypointStudio

uninstall

Installing from an UpdateSite.zip

Connectors can be installed manually by selecting the update site generated by DevKit:

  1. Open a command prompt or terminal and change directory to where the pom.xml file is for your project (in the Eclipse workspace).

  2. Run mvn clean package. This builds the connector. If the build succeeded, you should see the directory where the connector’s "UpdateSite.zip" was generated, which is usually what you point Studio.

  3. Click Help > Install New Software…​.

    install updatesite
  4. Click Add and in the new dialog look for the folder.

  5. Click the UpdateSite file, generated under your project’s target folder.

    install updatesite2

    You can either select the zip file named UpdateSite.zip or the folder update-site.

    install updatesite3

    A popup will open showing the task that is performed.

    install12
  6. Review the installation and update items, and accept the license agreement.

  7. Click Finish and restart Studio for the changes to be recognized, and your palette updated.

    JAR files are not signed during this run, so you will see a popup.
    security warning
  8. You can now use your connector in a Mule app.

    install2

Updating a Connector

To update your connector you can repeat the steps made for installing it.

AnypointStudio will detect it is an update and perform the corresponding actions.

Debugging Your Connector

After successfully installing your connector, you can start using your connector in a Mule application. You can add breakpoints in the source code of the connector to debug it. If your connector is a Community connector, the source code ships automatically in the installed connector. If your connector is an Enterprise connector, you need to manually attach the source code of your JAR.

To correctly debug your code, take into account that the Mule app you are running is using the latest installed version, so if you make changes, and you want to debug the Mule app, you need to re-install the connector.

Debugging your connector when running your test is as simple as debugging any Java class.

You just need to set a breakpoint in your connector code.

Sharing My Connector

A connector can be installed in Anypoint Studio as an "update site".

Update sites are used to organize and export features so they can be installed as a component/package into Eclipse applications.

Update site generation means the included features (along with all plug-ins part of those features) are exported into an installable form. The exported plug-ins and features are put into two folders: "plug-ins" and "features". Two other files, "content.xml" and "artifacts.xml" are also generated and contain metadata for the exported files that make installation easier. These files, along with "site.xml", collectively form an Eclipse update site. To make the update site available to others you must make all these files available in a shared directory or website.

When building a connector, DevKit generates the necessary resources for you, so that you don’t need to worry about generating it yourself.

You can use the Anypoint DevKit plugin to either install the connector on your current Studio, or export it as an update-site for others to use.

Connector Structure

In order to see how all the components are related, let’s create a Hello World connector:

  1. In Anypoint Studio, click File > New > Anypoint Connector Project or right-click your project name in Package Explorer and click New > Anypoint Connector Project:

    new connector 1
  2. Select the Connector Type you want to create. In this case, select SDK Based:

    new0
  3. Specify the name of your connector and click Finish:

    new connector 2

    This generates a project containing the structure and all required elements including a skeleton connector, images, sample docs, and basic tests for your connector.

    new connector 3
  4. Enable the DevKit view by clicking from the top bar Window > Show view > Other, and look for DevKit in the list.

    enable view

Your connector initially consists of message processors and user interface elements. Users can configure the UI elements in Anypoint Studio.

The DevKit makes it easy to install a connector in Studio. After you install it in Studio, users can search for your connector and drag it into a Mule flow.

Installing only requires right-clicking the name of the connector in Studio’s Package Explorer, and clicking Anypoint Connector > Install or Update, completing the prompts, and restarting Studio, as you can see at the install section. You can install at any time during coding. You can even install the starting skeleton connector.

Let’s check the structure of the skeleton connector.

In this image you can see how most of the code maps into UI elements.

Image1
Figure 1: View Structure and UI

In this example, you can check how the code matches to XML and other UI elements.

Image2
Figure 2: View configuration and XML

@ConnectionStrategy annotation is deprecated. Users instead should use @Config.

@Configurable and Parameter Modifiers

Optional parameters or configurable ones are elements that are not required, and therefore, there is no need for users to specify a value.

You can specify configurable fields (using @Configurable) inside your Connector Configuration classes, or as parameters of @Processor methods inside a @Connector class.


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Processor;
import org.mule.api.annotations.param.Optional;

//Inside the Configuration class

/**
 * Optional Field documentation
 */
@Configurable
@Optional
private String  optionalField;
//Getter and Setter of the field are required

//Inside your @Connector

/**
 * Optional parameter
 */
@Processor
public String sayHi(String firstName,@Optional String lastName ) {
    return "Hi "+firstName+" "+((lastName==null) ? "":lastName);
}

@Default

When you want an optional parameter or configurable you can avoid the use of @Optional and just use the @Default annotation.


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Processor;
import org.mule.api.annotations.param.Default;

//Inside the Configuration class

/**
 *  Field with Default
 */
@Configurable
@Default("Hi")
private String  greeting;
//Getter and Setter of the field are required

//Inside your @Connector

/**
 * Default parameter
 */
@Processor
public String sayHi(String firstName,@Default("Unknown") String lastName ) {
    return greeting+" "+firstName+" "+lastName;
}

Another very important use of the @Default annotation is when building a connector that has DataSense.

Adding OAuthV2

The Server can also provide the token using OAuth 2.0, instead of doing a login request.

In order to use OAuth in you connector you just need to annotate your strategy with the @OAuth2 annotation.

If you only had the OAuth2 Configuration your Config.java would look like:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package org.mule.modules.cookbook.config;

import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.oauth.*;
import org.mule.api.annotations.param.Default;

import com.cookbook.tutorial.client.MuleCookBookClient;

@OAuth2(configElementName = "oauth2", friendlyName = "OAuth 2.0", authorizationUrl = "http://devkit-cookbook.cloudhub.io/rest/oauth/authorize", accessTokenUrl = "http://devkit-cookbook.cloudhub.io/rest/oauth/accessToken")
public class OAuthConfig {

    private MuleCookBookClient client;

    @OAuthAccessToken
    private String accessToken;

    @Configurable
    @OAuthConsumerKey
    private String consumerKey;

    @Configurable
    @OAuthConsumerSecret
    private String consumerSecret;

    /**
     * URL used to connect to the service
     */
    @Configurable
    @Default("http://devkit-cookbook.cloudhub.io/soap")
    private String address;

    @OAuthPostAuthorization
    public void postAuthorize() {
        setClient(new MuleCookBookClient(getAddress()));
        getClient().setToken(getAccessToken());
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getAccessToken() {
        return this.accessToken;
    }

    public void setConsumerKey(String consumerKey) {
        this.consumerKey = consumerKey;
    }

    public String getConsumerKey() {
        return this.consumerKey;
    }

    public void setConsumerSecret(String consumerSecret) {
        this.consumerSecret = consumerSecret;
    }

    public String getConsumerSecret() {
        return this.consumerSecret;
    }

    public MuleCookBookClient getClient() {
        return client;
    }

    public void setClient(MuleCookBookClient client) {
        this.client = client;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

}

All our operations now need to be marked with the @OAuthProtected, even our @Source.

In our Connector for example you will see:


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @OAuthProtected
    @Processor
    @OnException(handler = CookbookHandler.class)
    @ReconnectOn(exceptions = { SessionExpiredException.class })
    public Map<String, Object> create(@MetaDataKeyParam(affects = MetaDataKeyParamAffectsType.BOTH) String type, @Default("#[payload]") @RefOnly Map<String, Object> entity)
            throws InvalidEntityException, SessionExpiredException {
        ObjectMapper m = new ObjectMapper();
        CookBookEntity input = null;
        if (type.contains("com.cookbook.tutorial.service.Recipe")) {
            input = m.convertValue(entity, Recipe.class);
        } else if (type.contains("com.cookbook.tutorial.service.Ingredient")) {
            input = m.convertValue(entity, Ingredient.class);
        } else {
            throw new InvalidEntityException("Don't know how to handle type:" + type);
        }
        return m.convertValue(this.getConfig().getClient().create(input), Map.class);
    }

Check full source code.

See:

Multiple Configurations

If you want to have multiple connection configurations there are two things to take into account:

  • All Configurations either need to implement the same interface or have a common parent class.

  • The connector Config field needs to be declared either as the interface or as the common parent class.

If your configuration has common fields, they can be defined at a parent class.

In our connector, after the refactor the classes would look like this:

uml-model

See:

Adding @Source

What is a Source?

In some cases it is necessary to create Message Sources instead of Message Processors.

Basically, a Message Source receives or generates new messages to be processed by Mule. One of the use cases for a Message Source is implementing Streaming APIs. The @Source annotation marks a method inside a @Connector annotated class as callable from a Mule flow and capable of generating Mule events. Each marked method will have a Message Source generated. The method must receive a SourceCallback as one of its arguments that represents the next message processor in the chain. It does not matter the order in which this parameter appears as long it is present in the method signature.

Implementing a Message Source

As an example, we are going to use the GetRecentlyAdded method.

  1. Inside your @Connector type source and use Ctrl + Space bar to display the templates.

    source template
  2. Create a @Source that consumes the Get Recently Updated on the Connector using a callback

    
                
             
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
        @Source(sourceStrategy = SourceStrategy.POLLING, pollingPeriod = 10000)
        public void getRecentlyAddedSource(final SourceCallback callback) throws Exception {
    
            if (this.getConfig().getClient() != null) {
                // Every 10 seconds our callback will be executed
                this.getConfig().getClient().getRecentlyAdded(new ICookbookCallback() {
    
                    @Override
                    public void execute(List<Recipe> recipes) throws Exception {
                        callback.process(recipes);
                    }
                });
    
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
            }
        }
  3. Install this new version

  4. On your flow now just drag and drop the Cookbook Connector, and you will see that is automatically while generate a flow.

    source example
  5. Add a logger and debug to see that the payload now has your recipes.

    source debug
    
        
                   
                
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    &lt;?xml version="1.0" encoding="UTF-8"?&gt;
    
    &lt;mule xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:cookbook="http://www.mulesoft.org/schema/mule/cookbook" xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
          xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.6.1"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
    http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
    http://www.mulesoft.org/schema/mule/cookbook http://www.mulesoft.org/schema/mule/cookbook/current/mule-cookbook.xsd
    http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
    http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd"&gt;
        &lt;cookbook:config name="CookBook__Connection_Management_Config" username="admin" password="admin" doc:name="CookBook: Connection Management Config"/&gt;
        &lt;flow name="source-get-recently-added"&gt;
            &lt;cookbook:get-recently-added-source config-ref="CookBook__Connection_Management_Config" doc:name="CookBook (Streaming)"/&gt;
            &lt;logger message="#[payload]" level="INFO" doc:name="Logger"/&gt;
        &lt;/flow&gt;
    &lt;/mule&gt;

About Adding a Transformer

Transformers convert message payloads to formats expected by their destinations. Mule ESB provides many standard transformers, which users can configure using elements and attributes in a Mule XML configuration file.

Sometimes it’s useful to build your own custom transformers.

Annotating a method with @Transformer signals the DevKit to export a method functionality as a transformer. You need to declare Transformers in classes annotated with @Module or @Connector and you can declare more than one transformer in a single class. It is possible to declare transformers, message processors, and message sources all in the same class.

A @Transformer annotated method must:

  • Be static

  • Be public

  • Not return void

  • Not return java.lang.Object

  • Receive exactly one argument

  • Be inside a class annotated with @Connector

Creating a Transformer

In our case, we are creating a transformer that converts a List<Recipe> into a List<Map<String,Object>>, that way we don’t need to modify our existing operation, and yet we can use the output of it in operations that receive a List<Map<String,Object>>.


          
       
1
2
3
4
5
6
7
    @Transformer(sourceTypes = { List.class })
    public static List<Map<String, Object>> transformJsonToComments(List<Recipe> list) {
        ObjectMapper mapper = new ObjectMapper();
        List<Map<String, Object>> result = mapper.convertValue(list, new TypeReference<List<Map<String, Object>>>() {
        });
        return result;
    }

Using Your Transformer in a Mule App

To use your transformer, just drag and drop it from the palette and build a flow.

  • Here we are using it explicitly to map transform the recipes into a Map.

    transformer explicit
    
        
                    
                 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    &lt;?xml version="1.0" encoding="UTF-8"?&gt;
    
    &lt;mule xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns:cookbook="http://www.mulesoft.org/schema/mule/cookbook" 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:spring="http://www.springframework.org/schema/beans" version="EE-3.6.1"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
    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/cookbook http://www.mulesoft.org/schema/mule/cookbook/current/mule-cookbook.xsd
    http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd"&gt;
        &lt;cookbook:config name="CookBook__Connection_Management_Config" username="admin" password="admin" doc:name="CookBook: Connection Management Config"&gt;
            &lt;reconnect/&gt;
        &lt;/cookbook:config&gt;
        &lt;http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration"/&gt;
    
        &lt;flow name="mule-appFlow"&gt;
            &lt;http:listener config-ref="HTTP_Listener_Configuration" path="/transform" doc:name="HTTP"/&gt;
            &lt;cookbook:get-recently-added config-ref="CookBook__Connection_Management_Config" doc:name="CookBook"/&gt;
            &lt;cookbook:recipes-to-maps doc:name="CookBook"/&gt;
            &lt;json:object-to-json-transformer doc:name="Object to JSON"/&gt;
        &lt;/flow&gt;
    &lt;/mule&gt;
  • Transformers can also be used implicitly as long as only one transformer is found to resolve the type.

    1. Define another transformer that can transform Recipe object into Map<String,Object>

      
                      
                   
      1
      2
      3
      4
      5
      6
      7
      
          @Transformer(sourceTypes = { Recipe.class })
          public static Map<String, Object> recipeToMap(Recipe recipe) {
              ObjectMapper mapper = new ObjectMapper();
              Map<String, Object> result = mapper.convertValue(recipe, new TypeReference<Map<String, Object>>() {
              });
              return result;
          }
    2. Install the connector.

    3. In your mule app create a flow that takes the first item of the list and add an update operation after it. Don’t use the transformer.

      transformer implicitly
      
          
                         
                      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      
      &lt;?xml version="1.0" encoding="UTF-8"?&gt;
      
      &lt;mule xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns:cookbook="http://www.mulesoft.org/schema/mule/cookbook" 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:spring="http://www.springframework.org/schema/beans" version="EE-3.6.1"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
      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/cookbook http://www.mulesoft.org/schema/mule/cookbook/current/mule-cookbook.xsd
      http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd"&gt;
          &lt;cookbook:config name="CookBook__Connection_Management_Config" username="admin" password="admin" doc:name="CookBook: Connection Management Config"&gt;
              &lt;reconnect/&gt;
          &lt;/cookbook:config&gt;
          &lt;http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8081" doc:name="HTTP Listener Configuration"/&gt;
      
          &lt;flow name="mule-appFlow"&gt;
              &lt;http:listener config-ref="HTTP_Listener_Configuration" path="/transform" doc:name="HTTP"/&gt;
              &lt;cookbook:get-recently-added config-ref="CookBook__Connection_Management_Config" doc:name="CookBook"/&gt;
              &lt;!-- Get the first item of the list --&gt;
              &lt;set-payload value="#[payload.get(0)]" doc:name="Set Payload"/&gt;
              &lt;!-- This operation is expecting a Map. Not a Recipe, but we are not using the transformer in the flow. The transformer will be called automatically --&gt;
              &lt;cookbook:update config-ref="CookBook__Connection_Management_Config" type="com.cookbook.tutorial.service.Recipe#0" doc:name="CookBook"&gt;
                  &lt;!-- Take the payload as input for the connector --&gt;
                  &lt;cookbook:entity ref="#[payload]"/&gt;
              &lt;/cookbook:update&gt;
              &lt;json:object-to-json-transformer doc:name="Object to JSON"/&gt;
          &lt;/flow&gt;
      &lt;/mule&gt;
    4. Run the example and see that the flow will execute successfully.

Using @Password

When using the @Password in a @Connect parameters, it generates a masked input field in the UI.


         
      
1
2
3
    @Connect
    @TestConnectivity
    public void connect(@ConnectionKey String username, @Password String password) throws ConnectionException {

In Studio this translates into:

password

Build a Connector for an API

APIs are exposed in several ways. To start using your API, you should set up a few things before you can use it inside your connector.

SDK Client

If you have an SDK all you need to do is to include the Maven dependency for your jar in the pom.xml

For example in our case, to consume the SDK for the Cookbook we can add the dependency.


          
       
1
2
3
4
5
6
7
<dependencies>
  <dependency>
    <groupId>foo.sdk.group.id</groupId>
    <artifactId>foo.sdk.artifact.id</artifactId>
    <version>${sdk.version}</version>
  </dependency>
</dependencies>

SOAP API

If you have a wsdl, the easiest way to build a connector is to create a client using CXF wsdl2java.

You can configure the CXF goal in your pom.xml file very easily. The full documentation is at the Apache CXF site.

For example, in your pom.xml file, you can add the following plus all required dependencies:


          
       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<build>
  <plugins>
    <!-- CXF Code generation -->
    <plugin>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-codegen-plugin</artifactId>
      <version>${cxf.version}</version>
      <executions>
        <execution>
          <phase>clean</phase> <!-- This is required for it to work with DevKit -->
          <goals>
            <goal>wsdl2java</goal>
          </goals>
          <configuration>
            <wsdlOptions>
              <wsdlOption>
                <wsdl>${basedir}/src/main/resources/wsdl/IMuleCookBookService.wsdl</wsdl>
                <autoNameResolution>true</autoNameResolution>
                <extendedSoapHeaders>false</extendedSoapHeaders>
                <extraargs>
                  <extraarg>-xjc-Xbg</extraarg>
                   <extraarg>-xjc-Xcollection-setter-injector</extraarg>
                  <extraarg>-p</extraarg>
                  <extraarg>org.mule.modules.wsdl.api</extraarg>
                </extraargs>
              </wsdlOption>
            </wsdlOptions>
          </configuration>
         </execution>
      </executions>
      <dependencies>
        <!-- Boolean Getters -->
        <dependency>
          <groupId>org.apache.cxf.xjcplugins</groupId>
          <artifactId>cxf-xjc-boolean</artifactId>
          <version>${cxf.version.boolean}</version>
        </dependency>
        <!-- Collection Setters -->
        <dependency>
          <groupId>net.java.dev.vcc.thirdparty</groupId>
          <artifactId>collection-setter-injector</artifactId>
          <version>0.5.0-1</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>

If you use the DevKit plugin it generates everything for you to get started, you just need to specify the WSDL location on your computer.

REST API

Write the request using any library that helps you make HTTP Requests.

We recommend using Jersey 2.11, which is provided in Mule version 3.6.0 and later.

To make sure you always use the right version, add the following dependency to your connector pom.xml.


          
       
1
2
3
4
5
6
7
8
<dependencies>
    <dependency>
        <groupId>org.mule.modules</groupId>
        <artifactId>mule-module-jersey</artifactId>
        <version>${mule.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Example GET request:


          
       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ClientConfig clientConfig = new ClientConfig();
Client client = ClientBuilder.newClient(clientConfig);
WebTarget webTarget = client.target("http://example.com/rest"); // (1)
WebTarget resourceWebTarget = webTarget.path("resource");
WebTarget helloworldWebTarget = resourceWebTarget.path("helloworld"); // (2)
WebTarget helloworldWebTargetWithQueryParam =
        helloworldWebTarget.queryParam("greeting", "Hi World!"); // (3)

Invocation.Builder invocationBuilder =
        helloworldWebTargetWithQueryParam.request(MediaType.APPLICATION_JSON_TYPE); // (4)

Response response = invocationBuilder.get(); // (5)
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
1 The client is ready to make a request to URL http://example.com/rest
2 Add paths for http://example.com/rest/resource/helloworld
3 Configure a query param. It looks like http://example.com/rest/resource/helloworld?greeting=Hi+World%21
4 Specify we want the reply in JSON format
5 Make a GET request

REST Server for Cookbook

If testing the three resources included in the Cookbook REST server, set up your connector to be SDK-based and select OAuth v2 as the authentication mechanism.

For more information, see the Jersey client documentation.