Sunday, May 8, 2011

Twitter and Spring Social in Magnolia

Tweets on 'summer' embedded in a page.
Integrations with social networks are everywhere. All the big social sites offer javascript widgets that you can easily drop into your pages and display things like recent tweets, facebook likes and so on. Though recently there's been some voices raised saying that these widgets sometimes don't blend nicely with the design of the rest of site. I've seen this especially on cms blogs reviewing high profile sites as they launch or relaunch with new designs.

So, last weekend I decided to find out how much work it would take to display a Twitter search using Spring Social with Blossom, the Spring integration for Magnolia CMS. Magnolia calls reusable components that you can snap into a page paragraphs. I wanted a paragraph that an author could add to any page to show relevant tweets about its topic. The author supplies the search query and the maximum number of tweets to display.On the right is the end result.

Twitter provides a RESTful API that can return JSON or ATOM. There's a lot of functionality in there and most of it requires OAuth authentication. For this example we'll do a simple search which doesn't require authentication. Spring Social offers an OAuth implementation that is compatible with Twitter and seems to be minimal effort to use. A twitter search using the API is done with a request like http://search.twitter.com/search.json?q=summer. Spring Social uses RestTemplate and parses the response using Jackson to return plain java objects representing the tweets.

Since Blossom is an extension of Spring Web MVC that lets Spring controllers be embedded into the content of a page the twitter search paragraph is implemented as a controller. The interface provided to the author to enable filling in the search query and the maximum number of tweets is provided by Magnolia using dialogs. By adding methods in our controller and annotating them Magnolia will call them when the dialog is to be displayed and for validation when its submitted. The view for displaying the tweets is a JSP.

In the source below you'll see that Spring Social makes things really easy since it will request the Twitter API and return the tweets with just one line of code.

@Controller
@Paragraph("Twitter")
public class TwitterParagraph {

  @RequestMapping("/twitter")
  public String twitter(ModelMap model, Content content) {

    String query = content.getNodeData("query").getString();
    int maxResults = Integer.parseInt(content.getNodeData("maxResults").getString());

    TwitterTemplate twitterTemplate = new TwitterTemplate();
    SearchResults searchResults = twitterTemplate.searchOperations().search(query, 1, maxResults);

    List<Tweet> tweets = searchResults.getTweets();
    model.put("query", query);
    model.put("tweets", tweets.subList(0, Math.min(tweets.size(), maxResults)));

    return "twitter";
  }

  @TabFactory("Settings")
  public void settingsTab(TabBuilder tab) {
    tab.addEdit("query", "Query", "Search phrase on Twitter");
    tab.addEdit("maxResults", "Max results", "Number of tweets to display at most");
  }

  @TabValidator("Settings")
  public void validateSettings(DialogTab tab) {
    if (!StringUtils.hasText(tab.getSub("query").getValue()))
      AlertUtil.setMessage("Query must not be empty");
    if (!NumberUtil.isPositiveInteger(tab.getSub("maxResults").getValue()))
      AlertUtil.setMessage("Max results must be a positive number");
  }
}

If you haven't used Magnolia or Blossom before this screenshot might be helpful to see what the dialog presented to the author looks like. The methods annotated with @TabFactory and @TabValidator are used to provide the user interface and validate the input.



And here's the source for the view.

<h2>Twitter</h2>
<div>Recently about <a href="http://twitter.com/search/${query}">${query}</a></div>
<ul class="tweets">
<c:forEach items="${tweets}" var="tweet">
    <li>
        <div>${tweet.text}</div>
        <div class="signature">
            <img src="${tweet.profileImageUrl}"/>
            <a href="https://twitter.com/${tweet.fromUser}">${tweet.fromUser}</a>
            <div class="date"><fmt:formatDate value="${tweet.createdAt}" pattern="d MMM HH:mm"/></div>
            <div style="clear:both;"></div>
        </div>
    </li>
</c:forEach>
</ul>

So how much work was it? I think I spent less than an hour setting it up and writing the java code, followed by a couple of hours on the view and css to get it decent enough to show in a blog post ;-)

To try it out you need to add their milestone repository since Spring Social has still to reach its 1.0 release. Here's the maven snippets you'll need.

<repository>
  <id>org.springframework.maven.milestone</id>
  <name>Spring Maven Milestone Repository</name>
  <url>http://maven.springframework.org/milestone</url>
  <snapshots>
    <enabled>false</enabled>
  </snapshots>
</repository>

<dependency>
  <groupId>org.springframework.social</groupId>
  <artifactId>spring-social-core</artifactId>
  <version>1.0.0.M3</version>
</dependency>
<dependency>
  <groupId>org.springframework.social</groupId>
  <artifactId>spring-social-twitter</artifactId>
  <version>1.0.0.M3</version>
</dependency>