Social mashups with Groovy

Mixing Groovy, Twitter, Google, and a bit of Ajax is socially acceptable

Online social networking is all the rage. In this article, learn how to build a social network with Google Maps, Twitter, Groovy, and Ajax. By combining a Google Map with location information that Twitter exposes, you can create a mashup that allows people to view Twitter in light of a particular location. The simple application this article builds lets users view a map of their Twitter friends — a geo-view of their network.

Andrew Glover, Co-Founder, ThirstyHead.com

Andrew GloverAndrew Glover is a developer, author, speaker, and entrepreneur. He is the founder of the easyb Behavior-Driven Development (BDD) framework and is the co-author of three books: Continuous Integration, Groovy in Action, and Java Testing Patterns. He teaches a wide variety of Groovy-, Grails-, and testing-related classes at ThirstyHead.com. You can keep up with Andy at thediscoblog.com, where he routinely blogs about software development.



24 February 2009

Also available in Chinese Japanese

Social networking over the Web is a huge trend these days. The skyrocketing popularity of applications like Twitter, Facebook, and LinkedIn attest to the fundamental human urge to connect with like-minded people. Something else is also all the rage these days: open application interfaces. Google and Twitter, for example, are wide open to the eyes and innovations of developers the world over. Both platforms offer APIs for querying, and ultimately integrating your applications with, virtually whatever you can dream up.

Mashups are the quintessential Web 2.0 application; they literally combine seemingly unrelated applications into a single functioning entity that seamlessly hides the turning gears behind the scenes that make it all work. The open APIs offered by entities vying to belong to the mashup community often rely on RESTful principles (see Resources), which makes building a mashup easier than you might think.

One popular mashup candidate these days is Google Maps. Google Maps is essentially a JavaScript library that lets you add the notion of where to an application. By giving Google Maps some location information (in the form of addresses or coordinates), you can build a map that signifies that location visually. If you've ever used Google's online mapping application (for instance to get driving directions), then you've already seen Google Maps in action.

Twitter is a popular social-networking application. It allows people to send a brief update (called a tweet) about anything they wish to a network of followers. (In Twitter, friends are people you follow, and followers are people following you.) This is often referred to as micro-blogging. You can query Twitter's API for various aspects of Twitter's data — for instance, tweets that meet specific criteria (say, ones referring to Java™ programming), or the names of a particular user's friends and even followers. On top of that, Twitter also stores location information because Twitter users have the option of providing their location.

Because Twitter exposes location information, you can combine Twitter with Google Maps to create a mashup that allows people to view Twitter in light of a particular location. That's what I'll show you in this article: you'll build a simple application that allows users to view a map of their friends — a geo-view of their network. You'll do this in three steps:

  1. Tie into Twitter via a Java-based third-party library.
  2. Implement a map via Google Maps.
  3. Tie it all together with Groovy via Groovlets and a bit of Ajax.

I'm assuming you already have Groovy installed on your system. I'll point you to the other tools you need as I proceed.

Twitter exposed

Twitter's extensive API supports quite a lot of functionality (see Resources). You can use it to search for tweets by location and keyword, obtain a list of a Twitter user's friends and followers, and even see those friends' and followers' tweets. The entirety of Twitter's API is RESTful: it exposes a series of Uniform Resource Identifiers (URIs) that map to functionality.

Because Twitter's API is publicly available, a number of developers have created libraries that facilitate working with Twitter. I'll use Twitter4J, a Java-based library that wraps the Twitter API. To follow along, download twitter4j-1.1.4.zip and add twitter4j-1.1.4.jar to your classpath. To do useful things with Twitter4J and indeed, Twitter, you'll need to create an account on Twitter. And you'll want to follow some people so you can make a geo-view of your friends.

Twitter4J in action

To learn more about the easyb behavior-driven development framework, check out Andrew Glover's tutorial "Drive development with easyb."

The central interface to the Twitter4J library is the Twitter object. For a given Twitter account, you can connect to the Twitter network by instantiating a new Twitter object with the account's username and password. I do this in Listing 1 (using easyb) for the johndoe user, whose password is 5555:

Listing 1. Using Twitter4J to connect to Twitter
scenario "Exploring Twitter4J's login functionality'", {
 given "an instance of Twitter4J", {
  twitter = new Twitter("johndoe", "5555")
 }
 then "the test method should return true indicating things are working", {
  twitter.test().shouldBe true
 }
}

Once you have a valid session, you can query (and even update) the Twitter instance for interesting information. For instance, I can get a list of an account's friends (that is, people that the Twitter account is following) via the getFriends call, as shown in Listing 2. This method returns a list of User objects — each one representing a valid Twitter account.

Listing 2. Using easyb to obtain a list of friends via Twitter4J
scenario "Twitter4J should support finding a user's network of friends", {
 given "an instance of Twitter4J", {
  twitter = new Twitter("johndoe", "5555")
 }
 then "the getFriends method should return a collection of users", {
  twitter.getFriends().size().shouldBeGreaterThan 0
 }
}

As you can see, Twitter4J's API is quite simple and intuitive.

From here, you can ascertain the location of the particular user via the getLocation method. This location is a simple name, such as Denver, Colorado or even simply Virginia. What's more, you can find out other interesting information, such as the profile picture a Twitter user has (via the getProfileImageURL call), the user's real name (via the aptly named getName method), screen name, and even (via the getDescription method) the short biography that a Twitter user can optionally provide.

In fact, a list of friends, a location, a profile image, and a short biography associated with that person is all you need to create an interesting mashup. I'll combine this information with Google Maps and show a geo-view of a Twitter account's network.


Setting up Google Maps

Getting started with Google Maps is fairly simple. First, you must obtain an API key. The key is free, but it's bound to a particular URL, so generating an API key for this article's application requires a bit of foresight. Because it's a Web application, I'll leverage a servlet container (such as Tomcat) and provide it (in development) with a context name, such as geotweet. Thus, the URL I need to have my Google Maps key associated with will be localhost:8080/geotweet. This key will only be valid for development purposes. When I decide to move the resulting code into production — with a URL like acmecorp.biz/geotweet — I'll need to generate another key.

When you generate a key, Google is nice enough to provide you with a snippet of code that can get you up and running. Copy the code into an HTML file, and you'll be good to go. For instance, Listing 3 is my initial index.html:

Listing 3. Starting point, courtesy of Google
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Geotweet!</title>
    <script src="http://maps.google.com/maps?file=api&v=2&key=YOURKEY"
      type="text/javascript"></script>
    <script type="text/javascript">

    //<![CDATA[

    function load() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(37.4419, -122.1419), 13);
      }
    }

    //]]>
    </script>
  </head>
  <body onload="load()" onunload="GUnload()">
    <div id="map" style="width:800px;height:500px"></div>
  </body>
</html>

As I said earlier, Google Maps is a JavaScript library; accordingly, you need to be aware of two events: the loading of a page and acting upon the map after page loading. Loading the page is the time when you essentially initialize Google Maps and accordingly display a map. Thus, in Listing 4, the HTML <body> element has an onload attribute. That's where I call the loadMap JavaScript function, which will load an instance of a Google Map.

Listing 4. Loading the map when the page loads
<body onload="loadMap()" onunload="GUnload()">

The loadMap function, shown in Listing 5, is defined in a <script> section above the <body> element:

Listing 5. The loadMap JavaScript function
 <script type="text/javascript">

        //<![CDATA[

        var map;

        function loadMap() {
            if (GBrowserIsCompatible()) {
                map = new GMap2(document.getElementById("containermap"));
                map.setCenter(new GLatLng(38.8920910, -77.0240550), 2);
                map.addControl(new GLargeMapControl());
            }
        }
 //]]>

</script>

After loading the map, you might want to act upon it, for instance by adding markers. In that case, you must first obtain a handle to the map and then call APIs that take coordinates on that map. Accordingly, in Listing 5, I've defined the map variable outside of the function itself.

Of course, I've got to display the map after the browser loads it. Consequently, in Listing 6, I define a <div> tag:

Listing 6. A simple <div> tag to hold a Google Map
<div id="containermap"></div>

Listing 7 contains a simple Cascading Style Sheet (CSS) for styling the <div> tag. It shifts the map slightly to the right, because I intend to add a simple <form> to the left (ideally, where a user would enter in the appropriate Twitter information).

Listing 7. Simple CSS for positioning a map
#containermap {
    position: absolute;
    margin: 5px 0px 0px 210px;
    height: 650px;
    width: 1000px;
}

At this point, my index.html file is viewable — without the need to deploy it into a servlet container, for instance. Just open up your file with a browser; it should look similar to Figure 1:

Figure 1. Initial map opened in a browser
plain map

Next, I'll add a simple little <form> to the left of the map. It has two fields that relate directly to what I demonstrated earlier with the Twitter4J library. That is, in order to do anything interesting, the application must log into a Twitter account with a username and a password. Listing 8 adds a <form> with username and password fields:

Listing 8. A simple form to capture information
<div id="container">
 <div class="form">
  <form action="" name="twitter">
   <p><label>twitter id:</label><input class="name" type="text" size="12"
       id="name" name="name" value=""/></p>
   <p><label>password:</label><input class="pword" type="password" size="12"
     id="pword" name="pword" value=""/></p>

   <div class="buttnz">
    <a href="javascript: doSubmit();">Map my friends!</a>
   </div>
  </form>
 </div>
</div>

Now that I've coded this form, the page looks a bit neater, as you can see in Figure 2:

Figure 2. Map with data-entry form
map with form

At this point, I'm ready to add some meat. I'll start with the functionality to map a Twitter account's friends. Doing so isn't terribly difficult, but it does require two additional technologies: some server-side processing and a bit of Ajax.


Server-side it up with Groovy

The Twitter4J library is — well — Java code, and I can't very well run that in my Web page. So I need some server-side processing now to put this application together. Accordingly, I'll use a lightweight framework of Groovy's called Groovlets. Groovlets are simply servlets without the structure of servlets. That is, you can write simple Groovy scripts and execute them within the context of a servlet. Your scripts have access to the normal objects you've come to love when coding servlets: ServletRequest, ServletResponse, and ServletContext, to name a few.

Getting yourself up and running with Groovlets couldn't be easier. Simply augment a web.xml file with some mappings, add the groovy-all-1.5.7.jar file from your Groovy installation to your Web application's WEB-INF/lib directory, and you are good to go.

For example, the two phrases in my web.xml file, shown in Listing 9, map requests ending in .groovy to Groovy's GroovyServlet, which handles the magic of Groovlets:

Listing 9. Adding Groovlets: As easy as adding a few fields to a web.xml file
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
      <servlet-name>GroovyServlet</servlet-name>
      <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
  </servlet>
  <servlet-mapping>
        <servlet-name>GroovyServlet</servlet-name>
        <url-pattern>*.groovy</url-pattern>
   </servlet-mapping>
</web-app>

Now any request going to a .groovy URI will invoke the GroovyServlet, which will grab the requested resource and execute it as is. The best part is that your Groovlets are simple scripts!


Serving up a mashup

Before I start to code any Groovlets, I need to think through two crucial issues. The first is the best way to obtain all my desired data from Twitter. I know how to do this, for sure, but I have the feeling I'll need to use it a few times; therefore, it makes sense to localize (no pun intended) my Twitter-specific logic into a service. Don't worry about the many connotations of the word service; in this case, the service is a simple class that abstracts specific behavior. For instance, my TwitterService is quite simple, as you can see in Listing 10:

Listing 10. A simple service for using the Twitter4J library
import twitter4j.Twitter

public class TwitterService {

 def getFriendsForTwitterer(username, password) {
  def twitter = getTwitter(password, username)
  return twitter.getFriends()
 }

 private def getTwitter(password, username) {
  return new Twitter(username, password)
 }

 def getLocationForUser(username, password) {
  def twitter = getTwitter(password, username)
  return twitter.getAuthenticatedUser().getLocation()
 }
}

The second issue is a bit more thought-provoking. Google Maps work with coordinates — you know, latitude and longitude — those numbers that essentially pinpoint anything on earth. Google Maps really doesn't know anything about Washington, D.C., for example. It only knows about latitude 38.8920910 and longitude -77.0240550. Google does, however, offer its own service to map between logical names and coordinates, which is known as geocoding. The geocoding service that Google offers is a simple URL that takes a location and other parameters and returns the coordinates for the requested location.

For instance, the URL http://maps.google.com/maps/geo?q=washington+DC&output=xml&key=YOUR_KEY returns an XML document like the one in Listing 11:

Listing 11. Google's geocoding response
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
 <Response>
  <name>washington DC</name>
  <Status>
   <code>200</code>
   <request>geocode</request>
  </Status>
 <Placemark id="p1">
  <address>Washington, DC, USA</address>
  <AddressDetails Accuracy="4" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0">
   <Country>
     <CountryNameCode>US</CountryNameCode>
 <CountryName>USA</CountryName>
 <AdministrativeArea>
  <AdministrativeAreaName>DC</AdministrativeAreaName>
  <Locality>
   <LocalityName>Washington</LocalityName>
  </Locality>
 </AdministrativeArea>
    </Country>
   </AddressDetails>
   <ExtendedData>
    <LatLonBox north=
        "38.9951100" south="38.7915140" east="-76.9093960" west="-77.1199010"/>
   </ExtendedData>
   <Point>
     <coordinates>-77.0240550,38.8920910,0</coordinates>
   </Point>
  </Placemark>
 </Response>
</kml>

Accordingly, I can code a service that takes the location string provided from Twitter and returns coordinates. This service is slightly more complicated, because of the fact that a Twitter account's location is optional. That is, users do not need to provide it, and Twitter does not attempt to validate it. Therefore, even if an account actually does provide a String value, it might be invalid, or Google might return more than one set of coordinates for it. What's more, some users use special applications (such as Twitter clients on their mobile phones) that update their location on a frequent basis with actual coordinates. So, the GoogleMapsService does a bit of cleanup before returning coordinates, as shown in Listing 12:

Listing 12. Using Google's geocoding service
package org.disco.geotweet.service

public class GoogleMapsService {

 private static String KEY = "YOUR_KEY"

 def getLocationCoordinates(location) {
  if (location.contains("iPhone:")) {
   //iPhone: 39.035248,-77.138687
   def iloc = location.replace("iPhone: ", "")
   def scoords = iloc.split(",")
   return [latitude: scoords[0], longitude: scoords[1]]
  } else {
   def encdLoc = URLEncoder.encode(location)
   def response = 
      "http://maps.google.com/maps/geo?" +
     "q=${encdLoc}&output=xml&key=${KEY}".toURL().getText()
   def root = new XmlSlurper().parseText(response)
   if (root.Response.Placemark.size() == 1) {
    def coords = root.Response.Placemark.Point.coordinates.text()
    def scoords = coords.split(",")
    if (scoords.size() > 1) {
     return [latitude: scoords[1], longitude: scoords[0]]
    } else {
     return [:]
    }
   } else {
    return [:]
   }
  }
 }
}

As you can see in Listing 12, I need to code more than one check for blank values. Note too how easy it is to parse Google's XML response using Groovy's XmlSlurper.

JSON

JSON (see Resources) is an alternate format for Web applications similar to XML but much less verbose. It is increasingly becoming the lingua franca of the Web, thanks to its lightweight nature. For a handy library that makes server-side processing and creation of JSON much easier, download JSON-lib if you're following along.

With the two services coded, it's time to link things together via Groovlets. First and foremost, it makes sense to center the map on the Twitter account's own location. Using the services I've just coded to do the heavy lifting, Listing 13 creates a Groovlet that takes two parameters — the Twitter account's username and password — and returns a JavaScript Object Notation (JSON) representation of that account's coordinates:

Listing 13. Centering a Google Map on a Twitter account
import net.sf.json.JSONObject
import org.disco.geotweet.service.GoogleMapsService
import org.disco.geotweet.service.TwitterService

def name = request.getParameter('username')
def pword = request.getParameter('password')

def coordinates = getJSONLatAndLng(name, pword)

println coordinates

def getJSONLatAndLng(name, password) {
 def googleMapsSevice = new GoogleMapsService()
 def twitterService = new TwitterService()
 def loc = twitterService.getLocationForUser(name, password)
 def coords = googleMapsSevice.getLocationCoordinates(loc)
 return this.getJSONForCoords(coords)
}

def getJSONForCoords(coords) {
  return new JSONObject().element("latitude", coords.latitude)
    .element("longitude", coords.longitude)
}

As you can see in Listing 13, this Groovlet is quite simple. Note too how Groovlets have implicit access to a request object and how using println suffices for returning a response.

Lastly, I need a way to return essentially the same information, just a lot more of it. That is, I need to return a JSON format of a Twitter user's friends' locations. While I'm at it, the JSON response will contain the URL to the user's image, name, and short biography. That way, when I put this information on the instance of the Google Map, I can make the marker a bit more interesting. But before I get to enhancing the map, I'll need to code the friends Groovlet, as shown in Listing 14:

Listing 14. A Groovlet for building a list of friends
import net.sf.json.JSONArray
import net.sf.json.JSONObject
import org.disco.geotweet.service.GoogleMapsService
import org.disco.geotweet.service.TwitterService

def name = request.getParameter('username')
def pword = request.getParameter('password')

def output = getJSONFriends(name, pword)

println output

def getJSONFriends(name, password) {
 def friends = getTwitterFriends(name, password)
 def output = new JSONObject().element("friends", new JSONArray())
 def gservice = new GoogleMapsService()
 friends.each {
  def location = it.getLocation()
  def pictureURL = it.getProfileImageURL().toString()
  def bio = it.getDescription()
  def tname = it.getName()
  if (location.size() > 0) {
   def profile = gservice.getLocationCoordinates(location)
   profile.pic = pictureURL
   profile.bio = bio
   profile.name = tname
   if (profile.size() > 0) {
    output.accumulate("friends", profile)
   }
 }
}

return output.toString() //JSON format
}


private def getTwitterFriends(name, password) {
 def service = new TwitterService()
 return service.getFriendsForTwitterer(name, password)
}

As you can see in Listing 14, I'm leveraging the GoogleMapsService (in concert with the TwitterService) to obtain a map (the profile variable) containing the coordinates of each Twitter account's friends. The map is augmented (via profile.pic = pictureURL) with some additional information that ultimately gets formatted into JSON and returned to the browser.

With those two Groovlets and the two services handling both Twitter and Google Maps logic, I'm done with the server-side portion of the mashup.


Leveraging Ajax for a smooth UI

The final step to get everything playing together is to leverage a bit of JavaScript to invoke the two Groovlets and accordingly update the Google Map residing on the page.

The two-field form on the left of the application's index.html page invokes the doSubmit method when someone clicks the "Map my friends" link. This function is where some heavy lifting will take place. I'm going to use Ajax to update the Web page asynchronously. This will provide a seamless user experience that doesn't require a page reload.

I'll leverage jQuery, a JavaScript library that provides a wealth of features for building slick UIs via JavaScript. This handy framework has a nice API for Ajax. For instance, I can easily center the map on the Twitter account's own location by invoking jQuery's getJSON call, as shown in Listing 15:

Listing 15. Using jQuery's getJSON function
function doSubmit() {
 var tid = document.twitter.name.value;
 var pwrd = document.twitter.pword.value;

 $.getJSON("locatetwitterer.groovy", {"username": tid, "password":pwrd}, function(json) {
  if(json.length > 0){
   map.setCenter(new GLatLng(json.latitude, json.longitude), 2);
  }
 });
}

The jQuery getJSON function in Listing 15 might look a bit strange, but it's quite easy once you understand what's going on:

  • The first parameter is the URL you wish to invoke. In this case, it's locatetwitterer.groovy, a file that's located in the Web application's root directory.
  • Next, it takes a map of parameters. Accordingly, the tid variable (holding the Twitter name) is associated with the username parameter. Likewise, the pwrd variable is mapped also.
  • The last parameter is what's known as a callback; that is, once the response has been received from the request made to locatetwitterer.groovy, the function defined in the callback is invoked. Accordingly, the length of the JSON response is checked; if there's some data, the latitude and longitude attribute values are obtained and provided to Google Maps' GLatLng object (which represents a location), and the map is correspondingly centered.

Add in the friends!

The next (and last) piece of business is adding markers on the centered map representing friends. I'll use the same jQuery getJSON call; however, this time around, my callback function will do a bit more. Listing 16 adds a listener to the Google Maps marker that contains some HTML. When someone clicks the marker, a little window will pop up that contains the selected friend's icon, name, and short biography.

Listing 16. Populating a map asynchronously
$.getJSON("locatefriends.groovy", {"username": tid, "password":pwrd}, function(json) {
 $.each(json.friends, function(i, item) {
  var marker = new GMarker(new GLatLng(item.latitude, item.longitude));
  map.addOverlay(marker)
  var picture = "<img src=\"" + item.pic + "\"/> " + item.name + "<br/>" + item.bio
  
  GEvent.addListener(marker, "click", function() {
   marker.openInfoWindowHtml(picture);
  });
 });
});

As you can see in Listing 16, this added aspect of the doSubmit function is a bit more involved but still easy to comprehend. First, within the callback, jQuery's each function iterates over the friends collection within the received JSON response. This function also has a callback; thus, for each iteration, some logic is applied to the data collected. As before, a new GLatLng object is created, and then a GMarker is overlaid on top of the map instance (giving you those little red dot-like icons on a Google Map). Figure 3 shows the map with the location markers:

Figure 3. A Google Map with locations
map with locations

Figure 4 shows the pop-up window that displays the friend's image and some text representing his or her name and biography (if provided):

Figure 4. Clicking the dot to reveal additional information
map with pop-up

Mashups are easy

As you can see, with some foresight and a bit of elbow grease, assembling a social mashup that combines Twitter and Google Maps is fairly straightforward — especially when you add Groovy and a spot of Ajax to the mix. Of course, you could do a lot more from here. I've only scratched the surface in terms of available features, including error handling. But the point remains: Open APIs and a bit of imagination open up a whole new world of applications and possibilities. The rest is up to you.

Resources

Learn

Get products and technologies

  • Twitter4J: This Java library lets you integrate applications with the Twitter service.
  • jQuery: Download the jQuery JavaScript Library.
  • JSON-lib: Download the JSON-lib library for programmatically building and parsing JSON.
  • Groovy: Download the latest Groovy version.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=372466
ArticleTitle=Social mashups with Groovy
publish-date=02242009