Java SDK-Based Connector Example
DevKit is compatible only with Studio 6 and Mule 3. To build Mule 4 connectors, see the Mule SDK documentation. |
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:
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 For authentication, the Twitter connector implements an interesting hybrid approach, defined in
|
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:
<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:
@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.
@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
:
/**
* 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:
@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:
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
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:
/**
* 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:
-
Return to the DevKit Development Steps to continue the development process described there
-
Build out the test suite to improve coverage, based on information in Developing DevKit Connector Tests
-
Build out the documentation examples to show more samples, based on information in Connector Reference Documentation