Blogs »
Update Status in Facebook, Twitter and LinkedIn with Spring-Social

Few weeks ago new project was released by SpringSource - Spring-Social. This project should simplify Spring MVC applications integration with social services like (currently supported) Facebook, Twitter, LinkedIn, TripIt.

There is already few tutorials displayed how to use it:

  • Original GreenHouse project (actually spring-social just looks like a part of Greenhouse extracted to be reused in other projects);
  • Excellent tutorial from gridshore;

But - both not very good: Greenhouse is really complex - it has many extra things and used too many (for me) nice but not well known (again - at least for me) spring features. So, it is not so easy to extract only spring-social related code.

Second are too simple - it is only for LinkedIn and not covered other supported Social Services (Facebook and Twitter for example).

So, I've decided to make own sample application and publish it as tutorial to make helpful for other developers. This application:

  • Implemented as simple as possible (sometimes code not very "right" - it is possible to make it cleaner with inheritance and so on - but I skipped it to keep code clearly demonstrating specific functionality);
  • Connected to Twitter, Facebook and LinkedIn;
  • Allowed user to update status in all supported systems.

As well as this sample demonstrating all three features promouted by Spring-Social:

  1. A set of social network templates for interacting with Twitter, Facebook, LinkedIn, TripIt, and Greenhouse;

  2. An OAuth-aware request factory for signing RestTemplate requests with OAuth authorization details;

  3. A web argument resolver for extracting Facebook user ID and access token information in a Spring MVC controller.

Source Code

Tutorial sources available from svn: svn.emforge.net/svnroot/akakunin-experiments/update-status, as well as you can watch them online.

What is in Background

Before we will go to see spring-social specific part of code, lets talk a little bit some major tools, frameworks and classes used in application. All of them quite standard - so, I will not stop on them too much:

  • maven to build;
  • Spring Framework (3.0.5);
  • Spring MVC for view (clean jsp used for views);
  • Spring Security (3.0.4) - used DB to store users;
  • JPA (via Hibernate provider);
  • build-in HSQL as DB.

Everything quite standard.

There is only one entity class: net.emforge.updatestatus.entity.User to store users in DB: name, password and field to store authentication information received from social services. One DAO to work with this entity and one Service. I hope everything simple and standard.

There is 2 pages: index with login and registration form (supported by UserController) and status (supported by SocialController) - it will be discussed later.

All of these allow us to create new users, login and navigate to status page, there we will test "social" functionality.

Register your applications

If you will want to test this sample locally - you will need to register your applications in the social services and store received application keys and security codes in config.properties. Also this class contains callbackURLs (url received callbacks from social services - please fix if you - for example - run your local server on some other port).

To register application you need to go:

Connect to LinkedIn and Twitter

Connection to LinkedIn and Twitter is done by same way via OAuth, so we will look at it on Twitter Example.

For communitication with Twitter as have very simple class TwitterProvider. This class receives twitter.apiKey, twitter.apiSecret & twitter.callbackUrl stored in properties and used for initializing OAuthService and TwitterTemplate (sping-social class).

So, to make authorization via OAuth with Twitter we need OAuthService:

    public OAuthService getOAuthService() {
        OAuthConfig config = new OAuthConfig();
        config.setRequestTokenEndpoint("https://api.twitter.com/oauth/request_token");
        config.setAccessTokenEndpoint("https://api.twitter.com/oauth/access_token");
        config.setAccessTokenVerb(Verb.POST);
        config.setRequestTokenVerb(Verb.POST);
        config.setApiKey(apiKey);
        config.setApiSecret(apiSecret);
        config.setCallback(callbackUrl);
     
        return new OAuth10aServiceImpl(
                new HMACSha1SignatureService(),
                new TimestampServiceImpl(),
                new BaseStringExtractorImpl(),
                new HeaderExtractorImpl(),
                new TokenExtractorImpl(),
                new TokenExtractorImpl(),
                config);
    }

 

In SocialController, responsible for processing requests on /status page (our main page in application) we add two handlers:

    @RequestMapping(value = "/connect/twitter", method = RequestMethod.GET)
    public String requestConnectionToTwitter(WebRequest request) {
        // get request token
        Token requestToken = twitterProvider.getOAuthService().getRequestToken();
        // store request token in session
        request.setAttribute("twitter_request_token", requestToken, WebRequest.SCOPE_SESSION);
       
        return "redirect:" + twitterProvider.getAuthorizeUrl() + "?oauth_token=" + requestToken.getToken();
    }
   
    /** Callback from twitter on success login
     *
     * @param verifier
     * @param request
     * @return
     */
    @RequestMapping(value = "/callback/twitter", method = RequestMethod.GET, params = "oauth_token")
    public String authorizeTwitterCallback(@RequestParam(value = "oauth_verifier", defaultValue = "verifier") String verifier,
                                    WebRequest request) {
        // get request token from session
        Token requestToken = (Token)request.getAttribute("twitter_request_token", WebRequest.SCOPE_SESSION);
       
        // get access token
        Token accessToken = twitterProvider.getOAuthService().getAccessToken(requestToken, new Verifier(verifier));
        String userName = getCurrentUser().getName();
        userService.updateTwitterAuthentication(userName, accessToken.getToken(), accessToken.getSecret());
       
        return "redirect:/status";
     
    }

 

First method processed get request to /connect/twitter (actually it is link "Connect to Twitter", and produced rdirect to the Twitter for authorization.

Second method processed callback from the Twitter on /callback/twitter (we pass this URL into initial request we sent), get authentication information (token and secret) and stored it into user database for later use with Twitter API.

Quite easy I hope.

Connect to Facebook

Authorization with Facebook working by a little bit another way.

To implement it we are placing into status.jsp special button "Connect to Facebook" with using this code:

<form id="fb_signin" action="<c:url value="/connect/facebook" />" method="post">
<div class="formInfo">
</div>
<div id="fb-root"></div>
<p><fb:login-button perms="email,publish_stream,offline_access" onlogin="$('#fb_signin').submit();" v="2" length="long">Connect to Facebook</fb:login-button></p>
</form>

<facebook:init />

Here is few important things:

  1. facebook:init tag - this tag implemented in spring-social, and it is required ${facebookProvider} bean to be defined in spring-context (we have it in FacebookProvider class)
  2. fb:login-button will display button for connection to Facebook
  3. form action (/connect/facebook) will be used as callback after success login

To make it working you also need to include facebook taglib into your jsp:

<%@ taglib uri="http://www.springframework.org/spring-social/facebook/tags" prefix="facebook" %>

And add jQuery into page, for example like this:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>

 

As result, "Connect to Facebook" button will be displayed, after pressing this button user will be redirected to facebook, after success login user will be rdirected to /connect/facebook - lets implement handler for it in our SocialController:

    @RequestMapping(value="/connect/facebook", method=RequestMethod.POST)
    public String connectAccountToFacebook(@FacebookAccessToken String accessToken,
                                           @FacebookUserId String facebookUserId) {
        if (facebookUserId != null && accessToken != null) {
            // store facebook information
            String userName = getCurrentUser().getName();
            userService.updateFacebookAuthentication(userName, accessToken, facebookUserId);           
        }
        return "redirect:/status";
    }

This method received @FacebookAccessToken and @FacebookUserId arguments - it is feature #3 from spring-social features discussed at the beginning. To make it working, we need to add special web argument resolver into our applicationContext.xml:

        <bean id="facebookWebArgResolver" class="org.springframework.social.facebook.FacebookWebArgumentResolver">
<constructor-arg name="apiKey" value="${facebook.appId}"/>
</bean>

Processing of callback are same as for Twitter and LinkedIn - we get authentication information(accessToken and facebookUserId) and store it for the user for future use with Facebook API

Send message to Twitter and Facebook

To send "Update Status" we have very simple form in our status page with one text area. This form processed by next handler in SocialController:

    @RequestMapping(value = "/status", method = RequestMethod.POST)
    public String sendStatus(@Valid StatusForm statusForm, BindingResult result, ModelMap modelMap) {
        User user = getCurrentUser();
       
        LinkedInTemplateExt linkedInTemplate = linkedInProvider.createTemplate(user);
        FacebookTemplate facebookTemplate = facebookProvider.createTemplate(user);
        TwitterTemplate  twitterTemplate = twitterProvider.createTemplate(user);
       
        // send message to LinkedIn
        if (linkedInTemplate != null) {
            linkedInTemplate.updateStatus(statusForm.getStatus());
        }
       
        // send message to Facebook
        if (facebookTemplate != null) {
            facebookTemplate.updateStatus(statusForm.getStatus());
        }
       
        // send message to Twitter
        if (twitterTemplate != null) {
            twitterTemplate.updateStatus(statusForm.getStatus());
        }
        return "redirect:/status";
    }

 

For sending status to Facebook and Twitter we create FacebookTemplate and TwitterTemplate classes (this is feature #1 in spring-social feature list) and call their methods updateStatus(String).

This templates are initizlied with related authentication information received for user during authorization into Facebook or Twitter

Send message to LinkedIn

With LinkedIn it was a little bit more complex. Problem is, LinkedInTemplate, provided by spring-social does not have method updateStatus. So, we should implement it by ourself (via using LinkedIn Api call http://api.linkedin.com/v1/people/~/person-activities). To do this I've:

  • Implemented own LinkedInTemplateExt class, inverited from original LinkedInTemplate;
  • In this class initialized own RestOperations class (since same from base class is not available for my class) with using OAuth1RequestSignerFactory.getRequestSigner (feature #2 from spring-social features);
  • Wrote LinkedInPersonActivity class to pass into updateStatus REST-method:
@XmlRootElement(name = "activity")
public class LinkedInPersonActivity {
    public LinkedInPersonActivity() {
    }
    public LinkedInPersonActivity(String body) {
        this.body = body;
    }
   
    @XmlElement(name = "content-type")
    String contentType = "linkedin-html";
   
    @XmlElement(name = "body")
    String body;
}
  • And implemented updateStatus method:
    public void updateStatus(String message) {
        LinkedInPersonActivity personActivity = new LinkedInPersonActivity(message);
       
        restOperationsExt.postForLocation("http://api.linkedin.com/v1/people/~/person-activities", personActivity);
    }

 

 By this way you can easily extend any basic Template class, provided by spring-social to add unimplemented API.

I hope this tutorial will be helpful. If you have any questions or suggestions - you can leave them as comments (required registration).

 

 

Alexey Kakunin

Twitter emforge

About Me I hope to make EmForge really useful for all developers

Activity Details
<b>53</b> Blog Entries 53 Blog Entries RSS
<b>451</b> Tasks 451 Tasks
<b>64</b> Friends 64 Friends