Contact Us 1-800-596-4880

Adding DataSense

DevKit is compatible only with Studio 6 and Mule 3. To build Mule 4 connectors, see the Mule SDK documentation.

Mule DataSense displays the metadata for the entities in a service. Although this feature is optional, Mule strongly recommends that you use DataSense in your connector to make its implementation much easier for users.

devkit steps operations

Prerequisites

This document assumes that you are familiar with the connector architecture presented in Anypoint Connector DevKit, and that you are familiar with DataSense from an end-user’s perspective.

Connectors with DataSense

Integration developers often spend great amounts of time simply trying to determine the parameters and types of data to pass to or are returned by a Web service. Having to resort to each API’s documentation to find out what these parameters are is an inefficient, fallible, and often frustrating experience. Thanks to DataSense, this information can be readily available within Anypoint Studio at design-time.

DataSense provides entities and their internal structure to Studio, which, in turn, presents this information to users employing the connector. DataSense also works with other Anypoint Studio features to:

  • Access a connector’s metadata so that when the context is right, Studio intelligently makes suggestions for expected values in fields returned by the connector.

  • Use DataMapper’s ability to automatically infer the input or output data within a mapping (when used in conjunction with a connector that is DataSense-enabled).

Learn more about DataSense.

The fundamental advantage of DataSense is the ability to extract metadata from the data model exposed by an application through the connector. The two key steps to implementing DataSense:

  1. Configure metadata retrieval – Obtains the metadata from the service and provides an implementation of the connector to supply this information.

  2. Configure metadata awareness – Defines how the operations are annotated so that Anypoint Studio is aware of the DataSense implementation and provides information about it to end users of the connector.

Static Data Model

A connector is considered to have a Static data model (also known as a "strongly typed" data model) when it exposes its entities as a POJO. For example, if you make use of an SDK with a certain set of classes, these are resolved and known at compile time.

In this case, metadata retrieval is straight forward: the POJO entity class definitions can be referenced and they can provide all the metadata needed in Java, available using introspection. Awareness is already implied by the strongly typed parameter in the processor.

Check the Twitter connector in GitHub for a working example.

Dynamic Data Model

When the connector has a dynamic data model (also known as "weakly typed") the metadata for a certain type is not immediately available, a certain MetaDataKey represents metadata that resolves at design or run-time. To support DataSense functionality for connectors with dynamic data models, you must implement additional functionality to create the metadata based on data provided from the application.

Metadata Retrieval

Dynamic metadata retrieval requires that you include two annotated metadata-related methods in a MetaDataCategory referenced by your connector which generate DataSense metadata for a dynamic schema.

  • @MetaDataKeyRetriever retrieves a list of all the entity type names from the connected service.

    @MetaDataKeyRetriever
    public List<MetaDataKey> getMetadataKeys() {  }
  • @MetaDataRetriever uses the list of metadata keys (retrieved by @MetaDataKeyRetriever) to retrieve the entity composition of each entity type.

    @MetaDataRetriever
    public MetaData getMetadata(MetaDataKey key) {  }

Metadata Awareness

This step makes the captured metadata accessible to the message processors. When implemented, a dropdown in the connector’s properties editor in Studio displays all the entities returned by @MetaDataKeyRetriever; each of these is coupled with the properties returned by @MetaDataRetriever.

DSimage

For this to happen, the message processor must include a method that receives the entity type as a parameter annotated with @MetaDataKeyParam. This method must also receive the entity data (that was returned by @MetaDataRetriever) on a parameter that is annotated as @Payload or @Default ("#[payload]").

The types of data expected and generated by this method vary depending on if your metadata is static or dynamic.

If your metadata is static, then the entity data is an Object:

public Object create(@MetaDataKeyParam String entityType, @Default("#[payload]") Object entityData) {
    }

If your metadata is dynamic, the entity data is a Map<String,Object>:

public Map<String,Object> create(@MetaDataKeyParam String entityType, @Default("#[payload]") Map<String,Object> entityData) {
    }

If your metadata is a list of dynamic objects, the entity data is a List<Map<String,Object>>:

public List<Map<String,Object>> getList(@MetaDataKeyParam String entityType, @Default("#[payload]") List<Map<String,Object>> entityData) {
    }

Adding DataSense Support To Your Connector for Dynamic Data Model

To implement DataSense in your connector, first create a @MetaDataCategory and bind it to the connector using @MetaDataScope.

Metadata Categories

To group DataSense resolvers, DevKit provides the annotation @MetaDataCategory that you can apply to a Java class. Within this Java class, define the metadata retrieving mechanism, that is, the methods annotated with @MetaDataKeyRetriever and @MetaDataRetriever, as a MetaDataScope.

For example, suppose you wish to offer a regular message processor and, additionally, a special message processor that provides access to a secret field on the Author entity named "books" that represents the Author’s written books. You can use metadata categories to bundle several distinct message processors into one connector, and display different groups of entities in each.

The example below displays a @MetaDataCategory class that contains both methods, and resides in a separate Java file. We then dig into the annotated methods. You may establish a link between this class and your connector module. The most common way of doing this is to use @Inject in your connector class as shown below.

  1. Create the MetaDataCategory class:

    import org.mule.common.metadata.*;
    import org.mule.common.metadata.builder.DefaultMetaDataBuilder;
    import org.mule.common.metadata.builder.DynamicObjectBuilder;
    import org.mule.common.metadata.datatype.DataType;
    
    import org.mule.api.annotations.components.MetaDataCategory;
    import org.mule.api.annotations.MetaDataKeyRetriever;
    import org.mule.api.annotations.MetaDataRetriever;
    
    @MetaDataCategory
    public class DefaultCategory {
    
        @Inject
        private MyConnector myconnector;
    
        @MetaDataKeyRetriever
        public List<MetaDataKey> getEntities() throws Exception {
            //Here we generate the keys
        }
    
        @MetaDataRetriever
        public MetaData describeEntity(MetaDataKey entityKey) throws Exception {
            //Here we describe the entity depending on the entity key
        }
    }
  2. Examine the imports:

    • The org.mule.common.metadata.* classes include the Mule classes for representing and managing metadata.

    • The org.mule.common.metadata.builder classes construct metadata representations (sets of objects that can be quite complex).

    • The org.mule.common.metadata.datatype.DataType class represents different object field datatypes and their properties.

  3. Bind this category to a @Connector or a @Processor using @MetaDataScope:

    /**
     * DataSense enabled Connector with multiple Categories
     *
     * @author MuleSoft, inc.
     */
    @MetaDataScope(DefaultCategory.class)
    @Connector(name = "my-connector", minMuleVersion = "3.6")
    public class MyConnector {
    ...
    
      @MetaDataScope(AdvancedCategory.class)
        @Processor
        public Map<String,Object> advancedOperation(@MetaDataKeyParam String entityType, @Default("#[payload]") Map<String,Object> entityData) {
           //Here you can use the books field in authors//
        }
    }

Example with Static Metadata

The following section demonstrates how to build a connector that draws data from a Web service with a static data model.

Download a full working example of this static-metadata connector from GitHub.

In this example, the Web service to which the connector connects is a library Web service. The Web service contains two types of elements: book and author.

The book element contains the following fields:

  • title

  • synopsis

  • author

The author element contains the following fields:

  • firstName

  • lastName

Static Metadata Retrieval

To retrieve static metadata:

  1. Obtain the metadata from the connector. Because the metadata is static, you don’t need to make a call the web service to obtain it, you can just hard-code the information in the connector. Inside your connector class, add a new method annotated with @MetaDataKeyRetriever:

    @MetaDataKeyRetriever
        public List<MetaDataKey> getEntities() throws Exception {
            List<MetaDataKey> entities = new ArrayList<MetaDataKey>();
            entities.add(new DefaultMetaDataKey("Book_id","Book"));
            entities.add(new DefaultMetaDataKey("Author_id","Author"));
            entities.add(new DefaultMetaDataKey("BookList_id","BookList"));
            return entities;
        }

    This method returns a list of the entity’s names. In this case, it retrieves a list with three keys: Book, Author, and BookList.

  2. Implement a @MetaDataRetriever method, which obtains a description of each of the entities returned by the previous method. The return type of this Java method must be MetaData and it must receive one MetaDataKey parameter. In this example, assume that the entity classes of the service exist locally. Book.class and Author.class can then be directly referenced in your code when describing them. You can call the interface DefaultMetaDataBuilder, provided by DevKit, to easily build a POJO.

    @MetaDataRetriever
    public MetaData describeEntity(MetaDataKey entityKey) throws Exception {
        //Here we describe the entity depending on the entity key
        if ("Author_id".equals(entityKey.getId())) {
            MetaDataModel authorModel =  new DefaultMetaDataBuilder().createPojo(Author.class).build();
            return new DefaultMetaData(authorModel);
        }
        if ("Book_id".equals(entityKey.getId())) {
            MetaDataModel bookModel =  new DefaultMetaDataBuilder().createPojo(Book.class).build();
            return new DefaultMetaData(bookModel);
        }
        if ("BookList_id".equals(entityKey.getId())) {
            MetaDataModel bookListModel =  new DefaultMetaDataBuilder().createList().ofPojo(Book.class).build();
            return new DefaultMetaData(bookListModel);
        }
        throw new RuntimeException(String.format("This entity %s is not supported",entityKey.getId()));
    }

    This method automatically describes Book, BookList and Author with all the public fields exposed by them.

The reason we use two different operations where one obtains the entities, and another obtains a description is that describing all the entities through a single method can result in an excessive number of API calls (you probably need one API call per entity).

Using two different operations is ideal for retrieving metadata from an external service.

Static Metadata Awareness

So far we have implemented the describing mechanism for all of the entities in the service we aim to connect. Now, make this information accessible to the message processors.

The method receives the operation’s type as a parameter annotated with @MetaDataKeyParam. The method also receives the entity data that was returned by @MetaDataRetriever on a parameter annotated as @Default("#[payload]").

@Processor
public Object create(@MetaDataKeyParam(affects = MetaDataKeyParamAffectsType.BOTH) String entityType, @Default("#[payload]") Object entityData) {
    if (entityData instanceof Book) {
        return createBook((Book) entityData));
    }
    if (entityData instanceof Author) {
        return createAuthor((Author) entityData));
    }
    throw new RuntimeException("Entity not recognized");
}
private Object createAuthor(Author entityData) {
    //CODE FOR CREATING NEW AUTHOR GOES HERE
    return null;
}
private Object createBook(Book entityData) {
    //CODE FOR CREATING A NEW BOOK GOES HERE
    return null;
}

The output metadata changes according to the entity type selected in Studio. This is especially useful when used in conjunction with a DataMapper transformer. Because of this method, all the entities returned by @MetaDataRetriever display in a dropdown in Studio.

DSimage

Also, the metadata about the entity can then be passed on to other Mule elements such as DataMapper.

image2

Example with Dynamic MetaData

The following section demonstrates how to build a connector that draws data from a Web service with a dynamic data model. The most practical way to implement metadata is always dynamically. Doing things this way, if the entity’s attributes in the service you connect to vary over time, your connector effortlessly adapts to the changes.

Download a full working example of this dynamic-metadata connector from GitHub.

In this example, as in the one above, the Web service to which the connector connects a book database. It contains two types of elements: books and authors, both contain the same fields as in the previous example.

Dynamic Metadata Retrieval

In this example, because you don’t have direct access to a POJO with the type structure, you must obtain this structure from the Web service itself. Use Map<String,Object> to represent the dynamic entities.

If you obtain the metadata dynamically through an API call, the @Connect method executes before the @MetaDataKeyRetriever method. This implies that end-users must first resolve any connection issues before gaining access to the metadata.
  1. Inside your connector class, add a new method annotated with @MetaDataKeyRetriever. (This method is no different from the one implemented with static metadata.)

    @MetaDataKeyRetriever
        public List<MetaDataKey> getEntities() throws Exception {
            List<MetaDataKey> entities = new ArrayList<MetaDataKey>();
            entities.add(new DefaultMetaDataKey("Book_id","Book"));
            entities.add(new DefaultMetaDataKey("Author_id","Author"));
            entities.add(new DefaultMetaDataKey("BookList_id","BookList"));
            return entities;
        }
  2. Implement a @MetaDataRetriever method. This obtains a description of each of the entities returned by the previous method. As in the previous example, this method uses the interface DefaultMetaDataBuilder, but this time it is called to build dynamic objects instead of POJOs.

    @MetaDataRetriever
    public MetaData describeEntity(MetaDataKey entityKey) throws Exception {
        //Here we describe the entity depending on the entity key
        if ("Author_id".equals(entityKey.getId())) {
            MetaDataModel authorModel =  new DefaultMetaDataBuilder().createDynamicObject("Author")
                    .addSimpleField("firstName", DataType.STRING)
                    .addSimpleField("lastName", DataType.STRING)
                    .build();
            return new DefaultMetaData(authorModel);
        }
        if ("Book_id".equals(entityKey.getId())) {
            MetaDataModel bookModel =  new   DefaultMetaDataBuilder().createDynamicObject("Book")
                    .addSimpleField("title",DataType.STRING)
                    .addSimpleField("synopsis",DataType.STRING)
                    .addDynamicObjectField("author")
                    .addSimpleField("firstName",DataType.STRING)
                    .addSimpleField("lastName",DataType.STRING)
                    .endDynamicObject()
                    .build();
            return new DefaultMetaData(bookModel);
        }
        if ("BookList_id".equals(entityKey.getId())) {
            MetaDataModel bookListModel =  new DefaultMetaDataBuilder().createList().ofDynamicObject("book").build();
            return new DefaultMetaData(bookListModel);
        }
        throw new RuntimeException(String.format("This entity %s is not supported",entityKey.getId()));
    }

Dynamic Metadata Awareness

Thus far, you have implemented the describing mechanism for all of the entities in the service you aim to connect. Now you must make this information accessible to the message processors.

The message processor must receive the operation’s type as a parameter annotated with @MetaDataKeyParam. (Studio displays the operations in a dropdown with all the entities returned by @MetaDataRetriever.) The message processor must also receive the entity data (returned by @MetaDataRetriever) as a Map<String,Object> parameter, annotated as @Default("#[payload]").

@Processor
public Map<String,Object> create(@MetaDataKeyParam String entityType, @Default("#[payload]") Map<String,Object> entityData) {
    if ("Book_id".equals(entityType)) {
        return createBook(entityData);
    }
    if ("Author_id".equals(entityType)) {
        return createAuthor(entityData);
    }
    throw new RuntimeException("Entity not recognized");
}
private Map<String, Object> createAuthor(Map<String, Object> entityData) {
    //CODE TO CREATE BOOK GOES HERE
    return entityData;
}
private Map<String, Object> createBook(Map<String, Object> entityData) {
    //CODE TO CREATE AUTHOR GOES HERE
    return entityData;
}

In this method, Studio displays all the entities returned by @MetaDataRetriever as items in a dropdown field.

image3

Also, the metadata about the entity can then be passed on to other Mule elements such as DataMapper.

image4