Spring Social makes it easy to add support for service providers that are not already supported by the framework. If you review the existing client modules, such as spring-social-twitter and spring-social-facebook, you will discover they are implemented in a consistent manner and they apply a set of well-defined extension points. In this chapter, you will learn how to add support for new service providers you wish to integrate into your applications.
The process of adding support for a new service provider consists of several steps:
Create a source project for the client code e.g. spring-social-twitter
.
Develop or integrate a Java binding to the provider's API e.g. TwitterApi
.
Create a ServiceProvider model that allows users to authorize with the remote provider and obtain authorized API instances e.g. TwitterServiceProvider
.
Create an ApiAdapter that maps the provider's native API onto the uniform Connection model e.g. TwitterApiAdapter
.
Finally, create a ConnectionFactory that wraps the other artifacts up and provides a simple interface for establishing connections e.g. TwitterConnectionFactory
.
The following sections of this chapter walk you through each of the steps with examples.
A Spring Social client module is a standard Java project that builds a single jar artifact e.g. spring-social-twitter.jar. We recommend the code structure of a client module follow the guidelines described below.
We recommend the code for a new Spring Social client module reside within the org.springframework.social.{providerId}
base package,
where {providerId} is a unique identifier you assign to the service provider you are adding support for.
Consider some of the providers already supported by the framework as examples:
Table 3.1. Spring Social Client Modules
Provider ID | Artifact Name | Base Package |
---|---|---|
spring-social-facebook | org.springframework.social.facebook | |
spring-social-twitter | org.springframework.social.twitter |
Within the base package, we recommend the following subpackage structure:
Table 3.2. Module Structure
Subpackage | Description |
---|---|
api | The public interface that defines the API binding. |
api.impl | The implementation of the API binding. |
connect | The types necessary to establish connections to the service provider. |
You can see this recommended structure in action by reviewing one of the other client modules such as spring-social-twitter:
Here, the central service API type, TwitterApi, is located in the api package along with its supporting operations types and data transfer object types. The primary implementation of that interface, TwitterTemplate, is located in the api.impl package (along with other package-private impl types have that been excluded from this view). Finally, the connect package contains the implementations of various connect SPIs that enable connections to Twitter to be established and persisted.
Spring Social favors the development of strongly-typed Java bindings to external service provider APIs. This provides a simple, domain-oriented interface for Java applications to use to consume the API. When adding support for a new service provider, if no suitable Java binding already exists you'll need to develop one. If one already exists, such as Twitter4j for example, it is possible to integrate it into the framework.
API developers retain full control over the design and implementation of their Java bindings. That said, we offer several design guidelines in an effort to improve overall consistency and quality:
Favor separating the API binding interface from the implementation. This is illustrated in the spring-social-twitter example in the previous section. There, "TwitterApi" is the central API binding type and it is declared in the org.springframework.social.twitter.api package with other public types. "TwitterTemplate" is the primary implementation of this interface and is located in the org.springframework.social.twitter.api.impl subpackage along with other package-private implementation types.
Favor organizing the API binding hierarchically by RESTful resource. REST-based APIs typically expose access to a number of resources in an hierarchical manner. For example, Twitter's API provides access to "status timelines", "searches", "lists", "direct messages", "friends", and "users". Rather than add all operations across these resources to a single flat "TwitterApi" interface, the TwitterApi interface is organized hierarchically:
public interface TwitterApi { DirectMessageOperations directMessageOperations(); FriendOperations friendOperations(); ListOperations listOperations(); SearchOperations searchOperations(); TimelineOperations timelineOperations(); UserOperations userOperations(); }
DirectMessageOperations, for example, contains API bindings to Twitter's "direct_messages" resource:
public interface DirectMessageOperations { List<DirectMessage> getDirectMessagesReceived(); List<DirectMessage> getDirectMessagesSent(); void sendDirectMessage(String toScreenName, String text); void sendDirectMessage(long toUserId, String text); void deleteDirectMessage(long messageId); }
API developers are free to implement their Java API binding with whatever REST/HTTP client they see fit. That said, Spring Social's existing API bindings such as spring-social-twitter all use Spring Framework's RestTemplate in conjunction with the Jackson JSON ObjectMapper and Apache HttpComponents HTTP client. RestTemplate is a popular REST client that provides a uniform object mapping interface across a variety of data exchange formats (JSON, XML, etc). Jackson is the leading Java-based JSON marshalling technology. Apache HttpComponents has proven to be the most robust HTTP client (if it is not available on the classpath Spring Social will fallback to standard J2SE facilities, however). To help promote consistency across Spring Social's supported bindings, we do recommend you consider these implementation technologies (and please let us know if they do not meet your needs).
Spring Social has adopted a convention where each API implementation class is named "{ProviderId}Template" e.g. TwitterTemplate. We favor this convention unless there is a good reason to deviate from it. As discussed in the previous section, we recommend keeping implementation types separate from the public API types. We also recommend keeping internal implementation details package-private.
The way in which an API binding implementation is constructed will vary based on the API's authorization protocol. For APIs secured with OAuth1, the consumerKey, consumerSecret, accessToken, and accessTokenSecret will be required for construction:
public TwitterTemplate(String consumerKey, String consumerSecret, String accessToken, String accessTokenSecret) { ... }
For OAuth2, only the access token should be required:
public FacebookTemplate(String accessToken) { ... }
Each request made to the API server needs to be signed with the authorization credentials provided during construction of the binding. This signing process consists of adding an "Authorization" header to each client request before it is executed. For OAuth1, the process is quite complicated, and is used to support an elaborate request signature verification algorithm between the client and server. For OAuth2, it is a lot simpler, but does still vary across the various drafts of the OAuth2 specification.
To encapsulate this complexity, for each authorization protocol Spring Social provides a ProtectedResourceClientFactory you may use to construct a pre-configured RestTemplate instance that performs the request signing for you. For OAuth1:
public TwitterTemplate(String consumerKey, String consumerSecret, String accessToken, String accessTokenSecret) { this.restTemplate = ProtectedResourceClientFactory.create(consumerKey, consumerSecret, accessToken, accessTokenSecret); }
An OAuth2 example:
public FacebookTemplate(String accessToken) { this.restTemplate = ProtectedResourceClientFactory.draft10(accessToken); }
Once the REST client has been configured as shown above, you simply use it to implement the various API operations. The existing Spring Social client modules all invoke their RestTemplate instances in a standard manner:
public TwitterProfile getUserProfile() { return restTemplate.getForObject(buildUri("account/verify_credentials.json"), TwitterProfile.class); }
A note on RestTemplate usage: we do favor the RestTemplate methods that accept a URI object instead of a uri String. This ensures we always properly encode client data submitted in URI query parameters, such as screen_name below:
public TwitterProfile getUserProfile(String screenName) { return restTemplate.getForObject(buildUri("users/show.json", Collections.singletonMap("screen_name", screenName)), TwitterProfile.class); }
For complete implementation examples, consult the source of the existing API bindings included in Spring Social.
The spring-social-twitter
and spring-social-facebook
modules provide particularly good references.
As part of the spring-social-test module, Spring Social includes a framework for unit testing API bindings. This framework consists of a "MockRestServiceServer" that can be used to mock out API calls to the remote service provider. This allows for the development of independent, performant, automated unit tests that verify client API binding and object mapping behavior.
To use, first create a MockRestServiceServer against the RestTemplate instance used by your API implementation:
TwitterTemplate twitter = new TwitterTemplate("consumerKey", "consumerSecret", "accessToken", "accessTokenSecret"); MockRestServer mockServer = MockRestServiceServer.createServer(twitter.getRestTemplate());
Then, for each test case, record expectations about how the server should be invoked and answer what it should respond with:
@Test public void getUserProfile() { HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.setContentType(MediaType.APPLICATION_JSON); mockServer.expect(requestTo("https://api.twitter.com/1/account/verify_credentials.json")) .andExpect(method(GET)) .andRespond(withResponse(jsonResource("verify-credentials"), responseHeaders)); TwitterProfile profile = twitter.userOperations().getUserProfile(); assertEquals(161064614, profile.getId()); assertEquals("kdonald", profile.getScreenName()); }
In the example above the response body is written from a verify-credentials.json file located in the same package as the test class:
private Resource jsonResource(String filename) { return new ClassPathResource(filename + ".json", getClass()); }
The content of the file should mirror the content the remote service provider would return, allowing the client JSON deserialization behavior to be fully tested:
{ "id":161064614, "screen_name":"kdonald" }
For complete test examples, consult the source of the existing API bindings included in Spring Social.
The spring-social-twitter
and spring-social-facebook
modules provide particularly good references.
If you are adding support for a popular service provider, chances are a Java binding to the provider's API may already exist. For example, the Twitter4j library has been around for awhile and provides a complete binding to Twitter's API. Instead of developing your own binding, you may simply wish to integrate what already exists. Spring Social's connect framework has been carefully designed to support this scenario.
To integrate an existing API binding, simply note the binding's primary API interface and implementation. For example, in Twitter4j the main API interface is named "Twitter" and instances are constructed by a TwitterFactory. You can always construct such an API instance directly, and you'll see in the following sections how to expose an instance as part of a Connection.
As described in the previous section, a client binding to a secure API such as Facebook or Twitter requires valid user authorization credentials to work. Such credentials are generally obtained by having your application conduct an authorization "dance" or handshake with the service provider. Spring Social provides the ServiceProvider<A> abstraction to handle this "authorization dance". The abstraction also acts as a factory for native API (A) instances.
Since the authorization dance is protocol-specific, a ServiceProvider specialization exists for each authorization protocol. For example, if you are connecting to a OAuth2-based provider, you would implement OAuth2ServiceProvider. After you've done this, your implementation can be used to conduct the OAuth2 dance and obtain an authorized API instance. This is typically done in the context of a ConnectionFactory as part of establishing a new connection to the provider. The following sections describe the implementation steps for each ServiceProvider type.
To implement an OAuth2-based ServiceProvider, first create a subclass of AbstractOAuth2ServiceProvider named {ProviderId}ServiceProvider. Parameterize <A> to be the Java binding to the ServiceProvider's's API. Define a single constructor that accepts an clientId and clientSecret. Finally, implement getApi(String) to return a new API instance.
See org.springframework.social.facebook.connect.FacebookServiceProvider
as an example OAuth2ServiceProvider:
public final class FacebookServiceProvider extends AbstractOAuth2ServiceProvider<FacebookApi> { public FacebookServiceProvider(String clientId, String clientSecret) { super(new OAuth2Template(clientId, clientSecret, "https://graph.facebook.com/oauth/authorize", "https://graph.facebook.com/oauth/access_token")); } public FacebookApi geteApi(String accessToken) { return new FacebookTemplate(accessToken); } }
In the constructor, you should call super, passing up the configured OAuth2Template that implements OAuth2Operations. The OAuth2Template will handle the "OAuth dance" with the provider, and should be configured with the provided clientId and clientSecret, along with the provider-specific authorizeUrl and accessTokenUrl.
In getApi(String), you should construct your API implementation, passing it the access token needed to make authorized requests for protected resources.
To implement an OAuth1-based ServiceProvider, first create a subclass of AbstractOAuth1ServiceProvider named {ProviderId}ServiceProvider. Parameterize <A> to be the Java binding to the ServiceProvider's API. Define a single constructor that accepts a consumerKey and consumerSecret. Finally, implement getApi(String, String) to return a new API instance.
See org.springframework.social.twitter.connect.TwitterServiceProvider
as an example OAuth1ServiceProvider:
public final class TwitterServiceProvider extends AbstractOAuth1ServiceProvider<TwitterApi> { public TwitterServiceProvider(String consumerKey, String consumerSecret) { super(consumerKey, consumerSecret, new OAuth1Template(consumerKey, consumerSecret, "https://twitter.com/oauth/request_token", "https://twitter.com/oauth/authorize", "https://twitter.com/oauth/access_token")); } public TwitterApi getApi(String accessToken, String secret) { return new TwitterTemplate(getConsumerKey(), getConsumerSecret(), accessToken, secret); } }
In the constructor, you should call super, passing up the the consumerKey, secret, and configured OAuth1Template. The OAuth1Template will handle the "OAuth dance" with the provider. It should be configured with the provided consumerKey and consumerSecret, along with the provider-specific requestTokenUrl, authorizeUrl, and accessTokenUrl.
In getApi(String, String), you should construct your API implementation, passing it the four tokens needed to make authorized requests for protected resources.
Consult the JavaDoc API of the various service provider types for more information and subclassing options.
As discussed in the previous chapter, one of the roles of a Connection is to provide a common abstraction for a linked user account that is applied across all service providers. The role of the ApiAdapter is to map a provider's native API interface onto this uniform Connection model. A connection delegates to its adapter to perform operations such as testing the validity of its API credentials, setting metadata values, fetching a user profile, and updating user status:
public interface ApiAdapter<A> { boolean test(A api); void setConnectionValues(A api, ConnectionValues values); UserProfile fetchUserProfile(A api); void updateStatus(A api, String message); }
Consider org.springframework.social.twitter.connect.TwitterApiAdapter
as an example implementation:
public class TwitterApiAdapter implements ApiAdapter<TwitterApi> { public boolean test(TwitterApi api) { try { api.userOperations().getUserProfile(); return true; } catch (BadCredentialsException e) { return false; } } public void setConnectionValues(TwitterApi api, ConnectionValues values) { TwitterProfile profile = api.userOperations().getUserProfile(); values.setProviderUserId(Long.toString(profile.getId())); values.setDisplayName("@" + profile.getScreenName()); values.setProfileUrl(profile.getProfileUrl()); values.setImageUrl(profile.getProfileImageUrl()); } public UserProfile fetchUserProfile(TwitterApi api) { TwitterProfile profile = api.userOperations().getUserProfile(); return new UserProfileBuilder().setName(profile.getName()).setUsername(profile.getScreenName()).build(); } public void updateStatus(TwitterApi api, String message) { api.timelineOperations().updateStatus(message); } }
As you can see, test(...) returns true if the API instance is functional and false if it is not. setConnectionValues(...) sets the connection's providerUserId, displayName, profileUrl, and imageUrl properties from TwitterProfile data. fetchUserProfile(...) maps a TwitterProfile onto the normalized UserProfile model. updateStatus(...) update's the user's Twitter status. Consult the JavaDoc for ApiAdapter and Connection for more information and implementation guidance. We also recommend reviewing the other ApiAdapter implementations for additional examples.
By now, you should have an API binding to the provider's API, a ServiceProvider<A> implementation for conducting the "authorization dance", and an ApiAdapter<A> implementation for mapping onto the uniform Connection model. The last step in adding support for a new service provider is to create a ConnectionFactory that wraps up these artifacts and provides a simple interface for establishing Connections. After this is done, you may use your connection factory directly, or you may add it to a registry where it can be used by the framework to establish connections in a dynamic, self-service manner.
Like a ServiceProvider<A>, a ConnectionFactory specialization exists for each authorization protocol. For example, if you are adding support for a OAuth2-based provider, you would extend from OAuth2ConnectionFactory. Implementation guidelines for each type are provided below.
Create a subclass of OAuth2ConnectionFactory<A> named {ProviderId}ConnectionFactory and parameterize A to be the Java binding to the service provider's API. Define a single constructor that accepts a clientId and clientSecret. Within the constructor call super, passing up the assigned providerId, a new {ProviderId}ServiceProvider instance configured with the clientId/clientSecret, and a new {Provider}ApiAdapter instance.
See org.springframework.social.facebook.connect.FacebookConnectionFactory
as an example OAuth2ConnectionFactory:
public class FacebookConnectionFactory extends OAuth2ConnectionFactory<FacebookApi> { public FacebookConnectionFactory(String clientId, String clientSecret) { super("facebook", new FacebookServiceProvider(clientId, clientSecret), new FacebookApiAdapter()); } }
Create a subclass of OAuth1ConnectionFactory<A> named {ProviderId}ConnectionFactory and parameterize A to be the Java binding to the service provider's API. Define a single constructor that accepts a consumerKey and consumerSecret. Within the constructor call super, passing up the assigned providerId, a new {ProviderId}ServiceProvider instance configured with the consumerKey/consumerSecret, and a new {Provider}ApiAdapter instance.
See org.springframework.social.twitter.connect.TwitterConnectionFactory
as an example OAuth1ConnectionFactory:
public class TwitterConnectionFactory extends OAuth1ConnectionFactory<FacebookApi> { public TwitterConnectionFactory(String consumerKey, String consumerSecret) { super("twitter", new TwitterServiceProvider(consumerKey, consumerSecret), new TwitterApiAdapter()); } }
Consult the source and JavaDoc API for ConnectionFactory and its subclasses more information, examples, and advanced customization options.