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

Java SDK-Based Connector Example

This example discusses the implementation of an Anypoint Connector for an API exposed through a Java SDK. Follow the process in this document to build a Java SDK-based connector for any service.

Java client libraries are in many cases the best option for integrating with a remote service. If the client library is officially supported by the application provider, or even if it is unofficial but widely used, it likely implements best practices for integrating with the application in common use cases. 

Prerequisites

This document assumes familiarity with the Anypoint connector architecture as described in Anypoint Connector DevKit. Furthermore, it assumes that you have created a new connector project as described in Creating an Anypoint Connector Project.

SDK-Based Connector Architecture

The overall architecture of a SDK-based connector looks like this:

java_client_architecture2 

The components of the connector include:

  • SDK - The Java SDK that exposes the authentication methods and the supported operations. See section Adding an SDK to a Connector Project

  • Entity Classes - Supporting data model entity classes, which typically are defined by the Java SDK

  • @Connector - The @Connector class that you implement based on the skeleton generated by DevKit

In practice, although the Java SDK usually communicates with a RESTful or SOAP-based web service, it encapsulates all the required client logic in Java, and the added work and complexity of building the client layer of the connector. The SDK may also hide non-standard behavior of badly designed web services behind a more manageable Java façade.

Example Connector

The example for this discussion is the MuleSoft Twitter connector, which is based on the unofficial but widely used twitter4j client library. The source code for the Twitter Connector on GitHub is available and referenced in the discussion section. 

The Twitter connector illustrates many aspects of DevKit functionality. The focus of this discussion is on the relationship between the SDK and the @Connector class.

For authentication, the Twitter connector implements an interesting hybrid approach, defined in TwitterConnector.java:

  • The Twitter4J client library implements its own OAuth support, which the Mule connector leverages

  • Because the connector does not use DevKit’s OAuth support, it is possible to use DevKit’s connection strategies.

Adding an SDK to a Connector Project

Depending upon how your SDK is delivered, you can add it to your project’s Maven POM file.

For example, Twitter4J can be added as a Maven dependency from the central Maven repository as follows:


         
      
1
2
3
4
5
<dependency>
    <groupId>org.twitter4j</groupId>
    <artifactId>twitter4j-core</artifactId>
    <version>3.0.3</version>
</dependency>

Defining the @Connector Class

The first step is building the @Connector class basic functionality around connection management and authentication, as indicated in the following sections.

Defining Entity Classes and Exceptions

In general, it is recommended that you define at least two exceptions for your connector: one to indicate connection and authentication-related failures, and another to indicate all other failures – including, for example, an exception for invalid arguments used in an operation. A separate exception package is a suitable place to define these exceptions.

Implementing Authentication and Connection Management

Authentication is implemented both in the @Connector class and in the SDK. Details of implementing authentication in the @Connector class depends upon the chosen authentication scheme and on the SDK authentication support.

You may add @Configurable properties on the @Connector class as described in Defining Connector Attributes and take advantage of the connection strategies as described in Connector Connection Strategies.

For instance, you should bind the connection lifecycle of your connector along with your SDK’s connection mechanisms.

Leveraging Twitter4J OAuth Support

If you are employing a natively-supported connection mechanism, you should go with DevKit support. Otherwise, you might employ a hybrid approach, as with Twitter. The Twitter connector implements an interesting hybrid approach, defined in GitHub at TwitterConnector.java:

  • The Twitter4J client library implements its own OAuth support, which the Mule connector leverages.

  • Because the connector does not use DevKit’s OAuth support, it is possible to use DevKit’s connection management framework.

Thus, we have a class definition without an @OAuth annotation:


          
       
1
2
3
4
@Connector(name = "twitter", schemaVersion = "3.1", description = "Twitter Integration",
  friendlyName = "Twitter", minMuleVersion = "3.6", connectivityTesting = ConnectivityTesting.DISABLED)
public class TwitterConnector implements MuleContextAware {
  ...

And a @Connect method with a @ConnectionKey set to the OAuth accessKey, and the usual associated @Disconnect@ValidateConnection, and @ConnectionIdentifier methods.


          
       
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
@Connect
public void connect(@ConnectionKey String accessKey, String accessSecret) throws ConnectionException{
      ConfigurationBuilder cb = new ConfigurationBuilder();
      cb.setUseSSL(useSSL);
      cb.setHttpProxyHost(proxyHost);
      cb.setHttpProxyPort(proxyPort);
      cb.setHttpProxyUser(proxyUsername);
      cb.setHttpProxyPassword(proxyPassword);

      HttpClientHiddenConstructionArgument.setUseMule(true);
      twitter = new TwitterFactory(cb.build()).getInstance();

      twitter.setOAuthConsumer(consumerKey, consumerSecret);
      if (accessKey != null) {
          twitter.setOAuthAccessToken(new AccessToken(accessKey, accessSecret));
          setAccessToken(accessKey);
          setAccessTokenSecret(accessSecret);
      }
  }
  ...

  @Disconnect
  public void disconnect() {
      twitter = null;
  }

  @ValidateConnection
  public boolean validateConnection() {
      return twitter != null;
  }

  @ConnectionIdentifier
  public String getConnectionIdentifier() {
      return getAccessToken() + "-" + getAccessTokenSecret();
  }

On the other hand, we have a series of @Processor methods that implement OAuth-related functionality, like getting and managing an access token by calling functions exposed by class twitter4j.Twitter:


          
       
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
/**
 * Set the OAuth verifier after it has been retrieved via requestAuthorization.
 * The resulting access tokens log to the INFO level so the user can
 * reuse them as part of the configuration in the future if desired.
 * <p/>
 * {@sample.xml ../../../doc/twitter-connector.xml.sample twitter:setOauthVerifier}
 *
 *
 * @param requestToken request token from Twitter
 * @param oauthVerifier The OAuth verifier code from Twitter.
 * @return Twitter AccessToken info.
 * @throws TwitterException when Twitter service or network is unavailable
 */
@Processor
public AccessToken setOauthVerifier(@Optional RequestToken requestToken, String oauthVerifier) throws TwitterException {
    AccessToken accessToken;
    if (requestToken != null) {
        accessToken = twitter.getOAuthAccessToken(requestToken, oauthVerifier);
    }
    else {
        accessToken = twitter.getOAuthAccessToken(oauthVerifier);
    }

    logger.info("Got OAuth access tokens. Access token:" + accessToken.getToken()
            + " Access token secret:" + accessToken.getTokenSecret());

    return accessToken;
}

/**
 * Start the OAuth request authorization process.
 */

@Processor
  public RequestToken requestAuthorization(@Optional String callbackUrl) throws TwitterException {
      RequestToken token = twitter.getOAuthRequestToken(callbackUrl);
      return token;
  }

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

  public String getAccessTokenSecret() {
      return accessTokenSecret;
  }

  public void setAccessTokenSecret(String accessTokenSecret) {
      this.accessTokenSecret = accessTokenSecret;
  }

And the @Processor methods that actually call Twitter operations do not use the @OAuthProtected annotation:


          
       
1
2
3
4
@Processor
  public User showUser() throws TwitterException {
      return twitter.showUser(twitter.getId());
  }

You can dig into this code and use a similar implementation pattern if you are working with a client library that provides its own OAuth support.

Adding an Operation to the @Connector Class

At this point you can start adding operations to the connector.  

With a SDK, the steps to add an operation include:

  • Importing any Java entity SDK-classes used as parameters or return value by the operation, as well as any exceptions the client library may raise

  • Adding a @Processor method on the @Connector class, that calls an operation on the client instance

Depending on your specific client class, you may need to add authentication functionality in the operation methods to handle authentication. 

Apply a Test-Driven Approach

Based on MuleSoft experience, most successful connector implementation projects follow a cycle similar to test-driven development when building operations on a connector:

  • Determine detailed requirements for the operation – entities (POJOs or Maps with specific content) that it can accept as input or return as responses; any edge cases like invalid values, values of the wrong type, and so on; and what exceptions the operation may raise

  • Implement JUnit tests that cover those requirements

  • Implement enough of your operation to pass those tests, including creating new entity classes and exceptions

  • Update your @Connector class and other code with the comments that populate the Javadoc related to the operation

Iterate until you cover all the scenarios covered in your requirements for a given operation. Then use the same cycle to implement each operation, until your connector functionality is complete.

If your SDK is well-documented, the expected behaviors for operations should be clear, and you may be able to get away with less unit testing for edge cases and certain exceptional situations – but bear in mind that your connector is only as reliable as the SDK you based it on.

You may ask, "When do I try my connector in Studio?" It is useful, as well as gratifying, to manually test each operation as you go, in addition to the automated JUnit tests. Testing each operation allows you to

  • See basic operation functionality in action as you work on it, which gives you a sense of progress

  • See how the connector appears in the Studio UI, something the automated unit tests cannot show you. For example, text from the Javadoc comments is used to populate tooltips for the fields in the dialog boxes in the connector

Manual testing provides the opportunity to polish the appearance of the connector, improve the experience with sensible defaults, and so on. 

However, this does not diminish the value of the test-driven approach. Many connector development projects have bogged down or produced hard-to-use connectors because of a failure to define tests as you define the operations, which it seems like (and is) more work up front, but does pay off – you get a better result, faster.

Implementing Operations

The Twitter connector implements a rich set of operations; some of the simpler ones are as follows:


          
       
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
/**
 * Returns a single status, specified by the id parameter below. The status's
 * author returns inline. <br>
 * This method calls http://api.twitter.com/1.1/statuses/show
 * <p/>
 * {@sample.xml ../../../doc/twitter-connector.xml.sample twitter:showStatus}
 *
 * @param id the numerical ID of the status you're trying to retrieve
 * @return a single {@link Status}
 * @throws twitter4j.TwitterException when Twitter service or network is unavailable
 * @see <a href="http://dev.twitter.com/doc/get/statuses/show/:id">GET
 *      statuses/show/:id | dev.twitter.com</a>
 */
@Processor
public Status showStatus(long id) throws TwitterException {
    return twitter.showStatus(id);
}

/**
 * Answers user information for the authenticated user
 * <p/>
 * {@sample.xml ../../../doc/twitter-connector.xml.sample twitter:showUser}
 *
 * @return a {@link User} object
 * @throws TwitterException when Twitter service or network is unavailable
 */
@Processor
public User showUser() throws TwitterException {
    return twitter.showUser(twitter.getId());
}

/**
 * Search for places that can be attached to a statuses/update. Given a latitude
 * and a longitude pair, or an IP address, this request returns a list of
 * all valid places that can be used as the place_id when updating a status.
 * <p/>
 * {@sample.xml ../../../doc/twitter-connector.xml.sample twitter:searchPlaces}
 *
 * @param latitude  latitude coordinate. Mandatory if no IP address is specified.
 * @param longitude longitude coordinate.
 * @param ip        the IP. Mandatory if no coordinates are specified.
 * @return a {@link ResponseList} of {@link Place}
 * @throws TwitterException when Twitter service or network is unavailable
 */
@Processor
public ResponseList<Place>
  searchPlaces(@Placement(group = "Coordinates") @Optional Double latitude,
               @Placement(group = "Coordinates") @Optional Double longitude,
               @Optional String ip) throws TwitterException {
    return twitter.searchPlaces(createQuery(latitude, longitude, ip));
}

private GeoQuery createQuery(Double latitude, Double longitude, String ip) {
    if (ip == null) {
        return new GeoQuery(new GeoLocation(latitude, longitude));
    }
    return new GeoQuery(ip);
}

Notes:

  • All of these operations call methods on the client instance stored in the twitter property. 

  • Annotations like @Optional, @Default, and @Placement are widely used to improve the configuration behavior of the connector and its appearance in Studio. 

  • Because the authentication is all handled by the Java client and a few methods in the @Connector class noted above, no authentication-related code is included in the @Processor methods. 

Creating JavaDoc and Samples for Operations

The JavaDoc for each operation includes a pointer to the sample code file:

../../../doc/twitter-connector.xml.sample

As well as the usual @param and @return comments, DevKit enforces the inclusion of these code samples, and checks the samples you provide against the parameters defined for those operations. See Connector Reference Documentation for details on creating the required documentation for each of your operations.

Creating Unit Tests for Operations

As you define each operation, you should create the unit tests that utilize it. The generated project skeleton created by the DevKit Maven archetype includes a unit test suite directory under ./src/test. DevKit defines a unit test framework based on JUnit. 

For details on creating unit tests, see Developing DevKit Connector Tests.

Next Steps

If you are merely reviewing the different connector implementation types, you can return to Connector Attributes and Operations and Data Models to review connector implementations that communicate directly with SOAP and RESTful Web services without using a pre-built SDK.

Once you have implemented your connector with its operations, as well as created some documentation and a test suite, you can: