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

Writing Custom Cloud Connectors

The DevKit Overview page introduces the idea of annotations. The @Connector annotation tells the DevKit that the Java class declaration that follows represents an endpoint that connects a Mule flow to an external entity. The entity might be a Web-based service like salesforce.com or LinkedIn, or it can be a legacy application on your intranet.

Connector Reference provides reference information about writing cloud connectors.

Your First Cloud Connector provides a quick tutorial.

Authentication

A special consideration for connectors is authentication. Often, the external entity requires a username and password on first connection, then returns an access token for the connector to use for subsequent interactions. The connector preserves the access token between calls. This pattern is called connection management. Using Connection Management provides more information about how Mule supports connection management.

The Mule application may not be the user, but may be acting for a user who wishes to keep the username and password secret. For such cases, the industry has developed the OAuth standard, which Mule supports.

Structuring Connector Code to Support Connection Management and OAuth

Sometimes you may wish to connect to an external entity that supports both connection management and OAuth. AS of DevKit Version 3.3, Mule provides a way to support both in a single connector JAR, with all of the common code in a base class. The different connector classes extend the base class.

Example

Here is an example simplified from the Mule connector to salesforce.com that uses connection management:


           
        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Connector(name = "sfdc", schemaVersion = "4.0", friendlyName = "Salesforce")
public class SalesforceModule {
 
    // a lot of @Configurable properties not-related to the connection management
 
    @Connect
    public synchronized void connect(@ConnectionKey String username, String password, String securityToken) { ... }
 
    @Disconnect
    public synchronized void destroySession() { ... }
 
    @Processor
    @InvalidateConnectionOn(exception = SoapConnection.SessionTimedOutException.class)
    public List<Map<String, Object>> query(@Placement(group = "Query") String query) throws Exception { ... }
}

Here is a similarly simplified example from the Mule connector to salesforce.com that uses OAuth:


           
        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Connector(name = "sfdc", schemaVersion = "4.0", friendlyName = "Salesforce")
@OAuth2(authorizationUrl = "https://login.salesforce.com/services/oauth2/authorize",
        accessTokenUrl = "https://login.salesforce.com/services/oauth2/token")
public class SalesforceModule {
 
    // a lot of @Configurable properties not-related to OAuth
 
    @Configurable
    @OAuthConsumerKey
    private String apiKey;
     
    @Configurable
    @OAuthConsumerSecret
    private String apiSecret;
 
    @Processor
    public List<Map<String, Object>> query(@OAuthAccessToken String accessToken,, @Placement(group = "Query") String query) throws Exception { ... }
}

Here is a version that combines connection management and OAuth:


           
        
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
public abstract class BaseSalesforceModule {
 
    // a lot of @Configurable properties not-related to the connection management
 
    @Processor
    @InvalidateConnectionOn(exception = SoapConnection.SessionTimedOutException.class)
    public List<Map<String, Object>> query(@Placement(group = "Query") String query) throws Exception { ... }
}
 
@Connector(name = "sfdc", schemaVersion = "4.0", friendlyName = "Salesforce")
public class SalesforceModule extends BaseSalesforceModule {
 
    @Connect
    public synchronized void connect(@ConnectionKey String username, String password, String securityToken) { ... }
 
    @Disconnect
    public synchronized void destroySession() { ... }
}
 
@Connector(name = "sfdc", schemaVersion = "4.0", friendlyName = "Salesforce", elementName = "config-with-oauth")
@OAuth2(authorizationUrl = "https://login.salesforce.com/services/oauth2/authorize",
        accessTokenUrl = "https://login.salesforce.com/services/oauth2/token")
public class SalesforceModule extends BaseSalesforceModule {
 
    @Configurable
    @OAuthConsumerKey
    private String apiKey;
     
    @Configurable
    @OAuthConsumerSecret
    private String apiSecret;
 
    @Configurable
    @OAuthAccessToken
    private String accessToken;
}

Here is how you can use both in the same Mule flow:


           
        
1
2
3
4
5
6
7
8
9
<sfdc:config name="Salesforce" username="${sfdc.user}" password="${sfdc.pass}" securityToken="${sfdc.token}"/>
 
<sfdc:config-with-oauth name="SalesforceWithOAuth" apiKey="${api.key}" apiSecret="${api.secret}"/>
 
<flow name="main">
     <sfdc:query config-ref="Salesforce" ... />
 
     <sfdc:query config-ref="SalesforceWithOAuth" ... />
</flow>

Connecting and Disconnecting

When you create a Cloud Connector, you must annotate exactly one method with the @Connect annotation and one with the @Disconnect annotation. The DevKit generates message processors corresponding to each of these.

Creating a connector also requires you to annotate two other methods. The @ValidateConnection annotation identifies a method the DevKit calls to see if the connection is already established before trying to establish it with the @Connect method. The @ConnectionIdentifier annotation identifies a method that provides an identifier to be used for logging. The method acquires, stores, and supplies a unique ID for the connection.

Additionally, the DevKit automatically creates message processors called authorize and unauthorize. The former authorizes the connection, and the latter rescinds the authorization and discards any saved access tokens.

When specifying the authorization process for a Cloud Connector, you must provide the URL of the external party providing the authorization. In the case of OAuth2, for example, you specify the URL as the authorizationUrl parameter in the @OAuth2 annotation. In some cases, you may wish to change the URL later in the process, perhaps to change environments or use a proxy. The following example — which uses a salesforce.com connector as a model — demonstrates how to change the URL later in the process.

Example

Here is how to specify the URL in the original annotation:


           
        
1
2
3
@OAuth2(authorizationUrl = "https://login.salesforce.com/services/oauth2/authorize",
        accessTokenUrl = "https://login.salesforce.com/services/oauth2/token")
public class SalesforceOAuthConnector extends BaseSalesforceModule {

Here is how to specify a new URL when creating the config element:


           
        
1
<sfdc:config-with-oauth authorizationUrl="newUrl"/>

This takes precedence over the value specified in the annotation.

Here is how to specify a new URL in the authorize message processor:


           
        
1
2
3
4
<flow name="authorizeFlow">
<http:inbound-endpoint ... />
<sfdc:authorize authorizationUrl="newUrl"/>
</flow>

This value takes precedence over either of the other two.

The State Parameter

The OAuth specification enables the caller to include a parameter called state, which the external entity returns with the access token. This helps the caller maintain state between the request and the callback.

The DevKit automatically makes state an attribute of the authorize message processor. To pass this parameter, set its value as in the following example:


          
       
1
2
3
4
<flow name="authorize">
    <http:inbound-endpoint ... >
    <sfdc:authorize ... state="xxx"/>
</flow>

Passing Additional Authorization Parameters

Sometimes authorization requires passing additional parameters with the authorization URL. Mule provides the authorizationParameters parameter for the @OAuth or @OAuth2 annotation. This parameter takes a list of authorization parameter names and types, specified as in the following example from the salesforce.com connector:


          
       
1
2
3
4
5
6
7
8
9
10
11
@Connector(name = "sfdc",
        schemaVersion = "5.0",
        friendlyName = "Salesforce",
        minMuleVersion = "3.3",
        configElementName = "config-with-oauth")
@OAuth2(authorizationUrl = "https://login.salesforce.com/services/oauth2/authorize",
        accessTokenUrl = "https://login.salesforce.com/services/oauth2/token",
        authorizationParameters = {
                @OAuthAuthorizeParameter(name = "display", type = SalesforceOAuthDisplay.class)
        })
public class SalesforceOAuthConnector extends BaseSalesforceModule

The example above shows an authorization parameter called display, which is of type SalesforceOAuthDisplay.class.

You can use any data type supported by the DevKit, except collections and complex types. The DevKit generates code to add the extra parameters to the generated authorization URL as query string parameters.

If the external entity passes parameters back as part of the authorization process, the DevKit provides a convenient way to access them. Use the @OAuthParameter annotation on each member of the Java class that you wish to use to hold those parameters, as in the following example:


          
       
1
2
3
4
5
@OAuthParameter(regex = "Mule Expression to obtain parameter 1")
private String parameter1;
 
@OAuthParameter(regex = "Mule Expression to obtain parameter 2")
private String parameter2;

The parameter is called regex for compatibility with earlier terminology, but it need not be a regular expression. It is an expression in the Mule Expression Language (MEL), which can be a regular expression or any other MEL expression.

Refreshing an Invalid Token

Mule provides a method for automatically refreshing an invalid access token. This requires a method that can recognize that a token is invalid and a regular expression for extracting the new token from the payload returned by the external entity in response to the request for an updated token.

The OAuthInvalidateAccessTokenOn annotation takes a parameter specifying the exception thrown by the access method if the token is invalid. The DevKit catches that exception and starts an automatically created subflow to obtain the updated token. Then, it retries the access with the updated token. If that fails, it throws an exception up to the main flow.

The annotation of the access routine is as in the following example:


          
       
1
2
3
@Processor
@OAuthInvalidateAccessTokenOn(exception = RuntimeException.class)
public void processor() { ... }

Specify the regular expression for extracting the updated token as in the following example:


          
       
1
2
3
@Connector(name = "oauth")
@OAuth2(refreshTokenRegex = "myRegex")
public class OAuthModule

Automatic Access Token Management

OAuth DevKit-generated cloud connectors have hooks for saving and restoring access tokens. This facilitates restoring state between Mule shutdowns. In DevKit 3.3, if the connector can identify the user, DevKit implements automatic saving and restoring of access tokens.

Use the @OAuthAccessTokenIdentifier annotation to mark a method inside a cloud connector as the one to be called by the DevKit to identify the user after authorization is complete.


          
       
1
2
3
4
@OAuthAccessTokenIdentifier
public String getUserId() {
return api.getUserId(myAccessToken);
}

Mule stores the ID of the user access token as a flow variable called OAuthAccessTokenId. Processors in the flow can use it as an argument in calls to protected resources. The following example demonstrates how to access the ID.


          
       
1
2
3
4
5
<flow name="authorize">
<http:inbound-endpoint ... />
<connector:authorize ... />
<logger level="INFO" message="The user identification is #[flowVars['OAuthAccessTokenId']]"/>
</flow>

The flow must save this information in, for example, a cookie or a session variable.

Mule DevKit creates an attribute called accessTokenId in each generated message processor that is protected by OAuth. This holds the idenfier of the access token, which is used to make the call. Note that the access token id is not the access token itself and does not represent only the access token. The following example illustrates how to set the attribute.


          
       
1
2
3
<flow name="xxx">
<connector:my-operation accessTokenId="#[flowVars['OAuthAccessTokenId']]"/>
</flow>

Mule DevKit persists the following information required to re-use an authorized connector:

  • The access token
    *
The acesss token secret (if it is OAuth 1.0a)
    *
The refresh token (if it is OAuth 2.0)
    *
Any information that was extracted during the service provider callback.

DevKit stores the information in the default user object store.

Mule DevKit 3.3.0 does not properly set the default user object store, so the connector initializes without specifying an object store. Mule DevKit 3.3.1 correctly sets the default user object store.

In the configuration element of any OAuth-enabled cloud connector capable of automatically managing access tokens, DevKit creates an element called oauth-store-config with a single attribute called objectStore-ref. The value of the attribute is a reference to an object store, as in the following example.


          
       
1
2
3
<connector:config ... >
<connector:oauth-store-config objectStore-ref="myObjectStore"/>
</connector:config>

Post-authentication Actions

After successful authentication using OAuth, the connector may need to perform setup tasks. Use the @OAuthPostAuthorization annotation to mark the method that performs these tasks, as in the following example:


         
      
1
2
@OAuthPostAuthorization
    public void postAuthorize() throws ConnectionException, MalformedURLException, AsyncApiException { ... }