Skip to main content

The busy Java developer's guide to Scala: Updating Twitter, with Scitter

Adding POST and DELETE operations to the Scitter library

Ted Neward, Principal, ThoughtWorks
Ted Neward photo
Ted Neward is a consultant with ThoughtWorks, a worldwide consultancy, and the principal of Neward & Associates, where he consults, mentors, teaches, and presents on the Java, .NET, XML Services, and other platforms. He resides near Seattle, Washington.

Summary:  The Scitter client library is almost ready to be released into the wild, but it needs a few finishing touches. In this installment of The busy Java developer's guide to Scala, Ted Neward shows you how to incorporate update, show, and delete functionality into the Scala-based library for accessing Twitter.

View more content in this series

Date:  20 Oct 2009
Level:  Intermediate PDF:  A4 and Letter (53KB | 18 pages)Get Adobe® Reader®
Activity:  5469 views

As I write this, summer is drawing to a close, the school year is starting up, and Twitter's servers are continuously pumping out updates from geeks and non-geeks around the world. For many of us in North America, thoughts are turning from beach parties to football, from outdoor pursuits to projects we can do inside. In keeping with this, I think it's a good time to revisit Scitter, the Scala client library for accessing Twitter.

About this series

Ted Neward dives into the Scala programming language and takes you along with him. In this developerWorks series, you'll learn what all the hype is about and see some of Scala's linguistic capabilities in action. Scala code and Java™ code will be shown side by side wherever comparison is relevant, but (as you'll discover) many things in Scala have no direct correlation to anything you've found in Java — and therein lies much of Scala's charm! After all, if Java could do it, why bother learning Scala?

If you've followed Scitter's development so far, you know that the library is able to read from the various Twitter APIs in order to look at a user's friends, followers, and timeline, among other things. What's missing is the ability to post status updates. In this final article about Scitter, we'll round out the library's functionality with some fun additions (endings and ratings) along with the essential methods update(), show(), and destroy(). Along the way, you'll learn more about the Twitter API and how well it interacts with Scala, and you'll also get some ideas for overcoming the inevitable programming challenges between the two.

Note that by the time this article reaches your eyes, the Scitter library will reside inside of a public source-control repository. I'll also include source code with this article, of course, but be aware that changes could be made to the source base. In other words, the code in the project repository may be subtly or drastically different from what you find here.

POSTing to Twitter

So far in the development of Scitter, we've focused on doing HTTP GET-powered operations, mainly because these calls are fairly easy, and I wanted to ease into the Twitter API. Adding POST and DELETE operations to the library will also be a big step in terms of visibility. Up until now, it was possible to run unit tests against your personal Twitter account without anybody knowing what you were up to. But once you start sending update messages, all the world will know that you're running Scitter unit tests.

If you continue to test Scitter, you'll want to create your own "test" account on Twitter. (The lack of any reasonable testing or mocking facility for Twitter is probably the biggest drawback to writing against the Twitter API.)

The Twitter API

If you're not already familiar with the Twitter API it's worth taking a few minutes to go check out the Twitter API wiki page at http://apiwiki. twitter.com/REST+API+Documentation. The fundamentals are simple: Parameters are passed as part of the URL query, responses can be in one of four formats (JSON, XML, ATOM, or RSS), and so on — but as with all APIs, the devil is in the details. Having the Twitter API open in a browser somewhere while you read this article should make it easier to focus on the Scala parts of the discussion.

The story so far ...

Before we launch into the library's new UPDATE functionality, let's review what we've created thus far. (I won't include the full source listing because Scitter is starting to get longer than is reasonable to display inline. Instead, have a look at the code in another window while reading.)

Roughly speaking, the Scitter library is broken into four parts:

  • The request and response types that are sent back and forth as part of the API (User, Status, and so on); these are modeled as case classes.
  • The OptionalParam types that are also part of the API in certain places; also modeled as case classes inheriting from a base OptionalParam type.
  • The Scitter object used for communication fundamentals and for anonymous (non-authenticated) access to Twitter.
  • The Scitter class, which holds a username and password for authenticated access to a given Twitter account.

Note that between the last article and this one, in order to keep the file sizes relatively manageable, I've broken the request/response types out into their own file.


Ending and rating

So now we're clear on our goals. We'll start working toward them by implementing two Twitter APIs that are "read-only": the end_session API (which closes down a user's session) and the rate_limit_status API (which describes how many posts a user account has left available for a particular period of time).

The end_session API is, like its cousin verify_credentials, a pretty simple API: simply call it with an authenticated request, and it will "close down" the working session. Implementing it on the Scitter class is relatively easy, as shown in Listing 1:


Listing 1. Implementing end_session on Scitter

package com.tedneward.scitter

{

  import org.apache.commons.httpclient._, auth._, methods._, params._

  import scala.xml._



  // ...

  class Scitter

  {

    /**

     *

     */

    def endSession : Boolean =

    {

      val (statusCode, statusBody) =

        Scitter.execute("http://twitter.com/account/end_session.xml",

          username, password)



      statusCode == 200

    }

  }

}

Well, okay: I lied. It's not quite so easy.

POSTing up

Unlike the other APIs we've worked with thus far in the Twitter API, end_session wants the incoming message to be sent with HTTP POST semantics. Right now, the Scitter.execute method does everything via GET, which means it has to somehow differentiate between those APIs that want a GET and those that want a POST.

Leaving that aside for just a moment, there's another obvious change: POSTed API calls also need to pass name/value pairs in to the execute() method. (Remember, in other API calls, using GET, all the parameters can and do appear as query parameters on the URL line; using POST, they appear in the body of the HTTP request.) In Scala, any time name/value pairs are mentioned, the Scala Map type leaps to mind, so the easiest way to think about modeling the data elements sent as part of a POST is to put them into a Map[String,String] and pass that.

For example, if we were passing in a new status message to Twitter, which requires the 140-characters-or-less message to be in a name/value pair called status, it would look like Listing 2:


Listing 2. Basic map syntax
val map = Map("status" -> message)

Given that, we can restructure the Scitter.execute() method to take a Map as a parameter. If the Map is empty, we can then assume that we should be using a GET instead of a POST, as shown in Listing 3:


Listing 3. Refactoring execute()


  private[scitter] def execute(url : String) : (Int, String) =

      execute(url, Map(), "", "")

    private[scitter] def execute(url : String, username : String,

	                             password : String) : (Int, String) =

      execute(url, Map(), username, password)

    private[scitter] def execute(url : String,

	                             dataMap : Map[String,String]) : (Int, String) =

      execute(url, dataMap, "", "")

    private[scitter] def execute(url : String, dataMap : Map[String,String],

                                 username : String, password : String) =

    {

      val client = new HttpClient()

      val method = 

        if (dataMap.size == 0)

        {

          new GetMethod(url)

        }

        else

        {

          var m = new PostMethod(url)


          val array = new Array[NameValuePair](dataMap.size)

          var pos = 0

          dataMap.elements.foreach { (pr) =>

            pr match {

              case (k, v) => array(pos) = new NameValuePair(k, v)

            }

            pos += 1

          }

          m.setRequestBody(array)

          

          m

        }


      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 

        new DefaultHttpMethodRetryHandler(3, false))

        

      if ((username != "") && (password != ""))

      {

        client.getParams().setAuthenticationPreemptive(true)

        client.getState().setCredentials(

          new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),

            new UsernamePasswordCredentials(username, password))

      }

      

      client.executeMethod(method)

      

      (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())

    }

The biggest change to the execute() method is the introduction of the Map[String,String] parameter, along with the "if" test based on its size. The test determines whether we're dealing with a GET or POST request. Because Apache Commons HttpClient wants the POST request body to be done in NameValuePairs, we iterate through the elements of the map using the foreach() call. We pass in the map key and value as a two-part tuple pr, and extract them into the local bound variables k and v, respectively, using those values as the constructor parameters to the NameValuePair constructor.

We could do all of this more easily by using the setParameter(name, value) API on PostMethod. I've chosen the approach in Listing 3 for pedagogical purposes: to show off the fact that Scala arrays, even though the array reference is marked as a val, are still mutable in the way that Java arrays are. Just keep in mind that it would be far better, in working code, to use the setParameter(name, value) method on PostMethod, for each (k,v) tuple.

Something else to note is that the Scala compiler doing its type inference does the right thing for the type of the "method" object returned by the if/else. Because Scala can see that if/else returns either a GetMethod or a PostMethod object, it chooses the immediate base type, HttpMethodBase, as the returned type for "method." This also means that any methods not available on HttpMethodBase are not accessible in the remainder of the execute() method. Fortunately, we don't need them, so it's unlikely that this will be a problem, at least for now.

One final issue lurks behind the implementation in Listing 3, having to do with the fact that I've chosen to use the Map to discriminate (whether the execute() method) is dealing with a GET or POST operation. If we ever need to use some other HTTP verb (such as PUT or DELETE), we'll have to refactor execute() once again. So far, this hasn't been a concern, but we'll want to keep it in mind as we go forward.

Test it

Before we implement this refactoring, let's run ant test to make sure that all the original GET-based request APIs still work — and they do. (This assumes no changes to the production Twitter API or the availability of the Twitter servers.) Everything works (or at least it did on my box), so implementing the new execute() method is pretty trivial:


Listing 4. Scitter v0.3: endSession

  def endSession : Boolean =

    {

      val (statusCode, statusBody) =

        Scitter.execute("http://twitter.com/account/end_session.xml",

          Map("" -> ""), username, password)


      statusCode == 200

    }

Can't get much simpler than that.

The next thing we want to do is implement the rate_limit_status API, which has both an authenticated and a non-authenticated version. We'll implement the method as rateLimitStatus on both the Scitter object and the Scitter class, as shown in Listing 5:


Listing 5. Scitter v0.3: rateLimitStatus

package com.tedneward.scitter

{

  object Scitter

  {

    // ...

	

    def rateLimitStatus : Option[RateLimits] =

    {

      val url = "http://twitter.com/account/rate_limit_status.xml"

      val (statusCode, statusBody) =

        Scitter.execute(url)

      if (statusCode == 200)

      {

        Some(RateLimits.fromXml(XML.loadString(statusBody)))

      }

      else

      {

        None

      }

    }

  }

  

  class Scitter

  {

    // ...

	

    def rateLimitStatus : Option[RateLimits] =

    {

      val url = "http://twitter.com/account/rate_limit_status.xml"

      val (statusCode, statusBody) =

        Scitter.execute(url, username, password)

      if (statusCode == 200)

      {

        Some(RateLimits.fromXml(XML.loadString(statusBody)))

      }

      else

      {

        None

      }

    }

  }

}

Also pretty straightforward, I think.


Updating

Now, armed with the new POSTable version of the HTTP communication layer, we can attack what is arguably the centerpoint of the Twitter API, the update call. Not surprisingly, it requires a POST with at least one parameter, named status.

The status parameter contains the not-longer-than-140-character message to be posted to the authenticated user's Twitter feed. There's also one optional parameter: in_reply_to_status_id, which gives the id of another update that the POSTed update is replying to.

That's pretty much it for the update call, as shown in Listing 6:


Listing 6. Scitter v0.3: update

package com.tedneward.scitter

{

  class Scitter

  {

    // ...


    def update(message : String, options : OptionalParam*) : Option[Status] =

    {

      def optionsToMap(options : List[OptionalParam]) : Map[String, String]=

      {

        options match

        {

          case hd :: tl =>

            hd match {

              case InReplyToStatusId(id) =>

                Map("in_reply_to_status_id" -> id.toString) ++ optionsToMap(tl)

              case _ =>

                optionsToMap(tl)

            }

          case List() => Map()

        }

      }

      
      val paramsMap = Map("status" -> message) ++ optionsToMap(options.toList)


      val (statusCode, body) =

        Scitter.execute("http://twitter.com/statuses/update.xml", 
           paramsMap, username, password)

      if (statusCode == 200)

      {

        Some(Status.fromXml(XML.loadString(body)))

      }

      else

      {

        None

      }

    }

  }

}

Probably the most "different" part of this method is the nested function defined inside of it — unlike the other Twitter API calls that use GET, Twitter expects the parameters to POST to appear inside the POSTed body, meaning we have to turn them into Map entries before calling Scitter.execute(). However, the default Map (from scala.collections.immutable) is an immutable creature, meaning we can combine Maps, but we can't add an entry into an existing Map. (Actually, we could but we'd prefer not to. See the "Mutable collections" sidebar for more about that.)

Mutable collections

It is possible to work with mutable collections in Scala, simply by importing the scala.collections.mutable package instead of scala.collections.immutable. Doing so runs the usual risks of working with mutable data, however, so the general Scala programming style suggests creating immutable collections from other immutable collections. If you really need or want to work with mutable collections, you can do so by importing scala.collections, and then referencing the Map (or other) collection types with a partial package-name prefix, a la mutable.Map or immutable.Map. This avoids any confusion when working with both mutable and immutable collections within a single scope block.

The easiest way to approach this little conundrum is to recursively attack the list (which is actually an Array[]) of OptionalParam elements passed in. We'll take each one apart and turn it into its own Map entry. Then we'll return a new Map (made up of the newly created Map and the Map returned from the recursive call) to optionsToMap.

After that, it's pretty trivial to pass the Array[] of OptionalParam to the optionsToMap nested function. We'll concatenate the returned Map to the one that we build containing the status message. Finally, we'll pass the new Map to the Scitter.execute() method, along with a username and password, for transmission to the Twitter server.

Incidentally, all of this takes longer to explain than to do in code, which indicates some fairly elegant programming.

Potential refactoring

What's happening with the optional parameters passed in to update is pretty identical, in theory, to the optional parameters passed in as part of the other, GET-based API calls; it's only the result format (name/value pairs suitable for POSTing as opposed to name/value pairs meant for a URL) that differs.

If it turns out that the Twitter API requires other HTTP verb support (PUT and/or DELETE being the two big ones that could be required), we could always make the HTTP parameter a specific parameter — perhaps another set of case classes — and have execute() take an HTTP verb, the URL, the map of name/value pairs, and (optionally) the username/password as five parameters. We could then turn the optional parameters into either a single string or a set of POSTed parameters as necessary. Just something to keep in mind for the future.


Showing

The show call displays a single Twitter status when given the id of the Twitter status to retrieve. Like update, it's pretty self-explanatory, shown in Listing 7:


Listing 7. Scitter v0.3: show

package com.tedneward.scitter

{

  class Scitter

  {

    // ...

	

    def show(id : Long) : Option[Status] =

    {

      val (statusCode, body) =

        Scitter.execute("http://twitter.com/statuses/show/" + id + ".xml",

		  username, password)

      if (statusCode == 200)

      {

        Some(Status.fromXml(XML.loadString(body)))

      }

      else

      {

        None

      }

    }

  }

}

Any questions?

Another way to show it

For those who want to experiment with pattern matching just a bit more, Listing 8 is another way to write the show() method:


Listing 8. Scitter v0.3: show redux

package com.tedneward.scitter

{

  class Scitter

  {

    // ...

	

    def show(id : Long) : Option[Status] =

    {

      Scitter.execute("http://twitter.com/statuses/show/" + id + ".xml", 
          username, password) match

      {

        case (200, body) =>

          Some(Status.fromXml(XML.loadString(body)))

        case (_, _) =>

          None

      }

    }

  }

}

Whether this is more clear than the if/else version is more a matter of aesthetics than anything else, but it is fair to say that it is probably more terse. (Chances are, the more the code reviewer sees the "functional" part of Scala, the more attractive this version is.)

The pattern-matched version does have one advantage over its if/else cousin, though: if new conditions (such as different error conditions or response codes from HTTP) end up being returned from Twitter, then the pattern-matched version will likely be more clear in its distinction between them. For example, if Twitter decides one day to return 400 response codes and an error message in the body itself to indicate some sort of formatting error (perhaps you didn't re-Tweet correctly), then the pattern-matched version can test both the response code and the contents of the body more easily (and clearly) than the if/else approach.

Also note that we could potentially use the form in Listing 8 to create some partially applied functions, requiring only URL and parameters. But, frankly, that would be a solution looking for a problem, so I won't go down that path.


And now: destroy

We'll probably want to give Scitter users the ability to undo an action that has just been done. For this, we need a destroy call, which will (what else?) delete a posted Twitter status, as shown in Listing 9:


Listing 9. Scitter v0.3: destroy

package com.tedneward.scitter

{

  class Scitter

  {

    // ...

	

    def destroy(id : Long) : Option[Status] =

    {

      val paramsMap = Map("id" -> id.toString())

    

      val (statusCode, body) =

        Scitter.execute("http://twitter.com/statuses/destroy/" + id.toString() + ".xml",

          paramsMap, username, password)

      if (statusCode == 200)

      {

        Some(Status.fromXml(XML.loadString(body)))

      }

      else

      {

        None

      }

    }

    def destroy(id : Id) : Option[Status] =

      destroy(id.id.toLong)

  }

}

And with that, I think we can consider the Scitter client library to be "alpha," at least to implement a simple Scitter client. (Which, in the grandest tradition of authorial spirit, I leave up to you, as an "exercise for the reader.")


Conclusion

Writing up the Scitter client library has been an interesting exercise. While it can't be said to be completely production ready, Scitter is certainly good enough to be used to implement a simple text-based Twitter client, which means it's ready to be cast loose. Releasing code into the wild is the best way to find out what people can do with it, and which features are needed in order to make it more useful.

I've committed the code from this and previous articles about Scitter as a first revision to the Scitter project home page, hosted by Google Code. Please feel free to download the library, play around with it, and let me know your thoughts. Bug reports, fixes, and suggestions are also welcome.

You needn't feel constrained to stick with my codebase, either. Following the development of Scitter over the past three articles should have left you with a good feel for how the Twitter API works. If you also have ideas about how to approach the API differently, then do it: toss Scitter out the door and build your own Scala client library. That's part of the fun of doing these little at-home projects, after all.

For now, we'll wave farewell to Scitter and start the happy hunt for a new project to solve with Scala. Have fun with that, and let me know if you find work programming in Scala!



Download

DescriptionNameSizeDownload method
Source code for this articlej-scala10209.zip812KBHTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

Ted Neward photo

Ted Neward is a consultant with ThoughtWorks, a worldwide consultancy, and the principal of Neward & Associates, where he consults, mentors, teaches, and presents on the Java, .NET, XML Services, and other platforms. He resides near Seattle, Washington.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=438554
ArticleTitle=The busy Java developer's guide to Scala: Updating Twitter, with Scitter
publish-date=10202009
author1-email=ted@tedneward.com
author1-email-cc=jaloi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers