Interacting with Twitter

In this section, we will update the chatrum application to enable some interaction with Twitter. What we're going to do is search for tweets based on a hashtag and a username. For that, we'll look for items in the chatrum that have special patterns, that is, words starting with a hash (#) or an at sign (@). First of all, we'll see how to use Twitter to retrieve information using a browser and the API specification.

The Twitter REST API provides an entry point from which it will be able to do a lot of search operations. This entry point is the URL http://search.twitter.com/search.json. At first glance we can guess that the operations will represent the response in JSON.

In order to search on a hashtag, this URL can be set with a search parameter named q that holds the hashtag, prefixed by the well-known # character. Of course, the request is a GET one.

So let's try this in our browser; it will help us later because we'll have the opportunity to analyze the output and see what data we can retrieve, where, and how:

Interacting with Twitter

The previous screenshot shows us how to create a query (note the %23 value before the playframework tag) using the Twitter REST API, and it also shows how a response is structured (encoded in JSON, as expected).

The result presents a lot of information that we won't need in our example. So we'll only use the results property. This property is a JSON array containing all of the tweets matching the query, and with each tweet having a certain amount of data. We'll continue to focus on the part we're interested in: the from_user and the text properties. These properties are the username of the tweeter and the tweet's text respectively.

The search on a particular username is exactly the same but the prefix has to be @ rather than # in the q parameter; meanwhile, the result has exactly the same structure. That's fine, we'll probably be able to share some code.

Based on that, let's now see how to create an action that will search Twitter and return its own representation of the result, so that it will be usable on the client side of our application.

For that, we'll first add a new dedicated controller named Twitter. This controller defines two actions:

  • searchTag(String tag): Searches Twitter for tweets tagged with the given tag
  • mentioning(String user): Searches Twitter for tweets mentioning the given username

However, as said earlier, some logic can be shared, so this controller will have another method called findAndSeek(String q), which is not an action by itself, but will contain the logic for searching on Twitter.

The following screenshot shows the skeleton of our Twitter controller:

Interacting with Twitter

The definition at this stage is quite obvious (the logic is hidden). The actions are simply calling the third method that contains the logic. As the parameters of searchTag and mentioning don't include the Twitter-specific characters, the actions are preparing the query before launching the search.

Before moving to the web service call, we'll define the route for each action:

Interacting with Twitter

Using the Twitter API

In the previous section, we set up our actions and routed them; let's now take a deeper look at how we can deal with the Twitter REST API—the definition of the findAndSeek method.

Its implementation will be split into three parts: the call to the Twitter API, the transformation of the result's structure into a custom one (adapted to our needs), and finally the execution of the whole thing.

The following screenshot shows the implementation of findAndSeek:

Using the Twitter API

We're now going to review each part separately. First, we create the request using the WS API:

Using the Twitter API

What's being done here? First, we've used the URL from WS to create a WSRequestHolder object using the base URL for Twitter's search, which we've done before.

What's still missing at this point is the query parameter that is necessary to specify what you want Twitter to search for. In the browser, it was provided as the query string parameter q. In this case however, we can simply set this parameter using the setQueryParameter method.

So far, we've defined the URL to the target and the parameter to be used; for our use case, the only other thing needed (as we don't need any authentication, for instance) is to end the definition by calling get() (one line using the fluent API of WS).

This will result in a Promise<WS.Response> response, that is, the HTTP GET hasn't yet been executed. We've just prepared the whole request, which is now ready to be sent. Also, it says that the type of the result will be a WS.Response response, but this is not the type of response we need in our interface. What we want is a custom JSON representation of the body of this response, as shown in the following screenshot:

Using the Twitter API

For those who aren't familiar (yet) with deferred computation such as Promise, this might look a bit strange. However, it's very simple.

First, recall that the result of the request is not yet there, but we still want to transform it. How can we do this? By using the map method on Promise.

This map method can be like registering a callback (at least for this particular case) on the result of the request. But, where such a callback is meant to be imperative (with side effects), a map method of Promise will register a function to be executed on the result of the initial request and might adapt it in such a way that the result of the whole Promise will change. An example is a process that promises to output a result of type String (Promise<String>), which we'll map on to an integer using map that invokes Integer.parseInt. The result won't be an instance of Promise<String>, but an instance of Promise<Integer>.

Note

Also, the result of this callback should be synchronous; if it should be asynchronous as well, we must use flatMap rather than map on Promise. Indeed, if we use map with a method returning a Promise<T> result type, we'll get a result of type Promise<Promise<T>>. In short, what we'll do with flatMap is get rid of the second Promise object, that is, it will flatten the result type. Talking about the real sense of map would require much more time and effort than it's worth. However, if you're interested in the underlying concepts, I'd recommend you learn about Functors .

The callback we have registered is a function (does that remind you a bit of AJAX?), in which Play! Framework 2 (Java) is an instance of the play.libs.F.Function<A,B> interface. This type enables us to define an execution logic that takes A as a parameter and returns B (well, a function from A to B...).

Our callback must take the result of the WS API call we have used, that is, Promise<WS.Response>, and we would like to set the action result, an instance of Result.

The cool thing now is to check the result type of this map application; it's still a Promise but the expected type is no longer WS.Response, but Result. Indeed, the Result type traversed the applications of get() and map, and is now a type-checked promise.

The implementation of Function itself is only a transformation between the Twitter's JSON structure to our custom one. However, the following statement should get our attention for a moment:

JsonNode json = response.asJson();

This statement is very similar to a request body's usage we had in actions.

For the following section, it's worth understanding the shape of the constructed custom representation of the tweets. The way to do this is to test one of them in our browser. But before that, let's end the code review by covering the very last part of it:

Using the Twitter API

A single line and its comment that says: take the instance of Promise<Result>, get the value in it, and return that value. However, what we did using get() is we asked the thread to block until Twitter answers (followed by the handle of the body as JSON and the transformation to the target's structure). That's bad! Where is the non-blocking feature of Play! 2 in this case? Actually, it's our fault, and will be covered in the next section.

Integrating chatrum with Twitter search

Now that we have implemented our actions, let's see them working in the browser. The following screenshot shows a search for mentions of the username @noootsab:

Integrating chatrum with Twitter search

Note

In the previous screenshot, we can see a rendered JSON. This is not a part of the Play! Framework 2, but the browser itself might be able to discover the content type and adapt its display.

The simplest form we can have is to represent a list of tweets for which we only want to retain the tweeter's name and the tweet itself.

Everything is in place now to have our chatrum integrated with Twitter searches. Actually, the server is now ready, but the client side needs to be updated too.

So the way we are going to integrate them is via the items that are shown in the chatrooms. These items could contain usernames (words preceded by @) or tags (words preceded by #). That's our entry point; we'll then parse the items in order and add markers, which enables them to be searchable on Twitter through simple clicks on them. Finally, the resulting tweets will be printed in a dedicated part of the page.

First we look at the items, which are rendered not only on the server side but also on the client side, and then we'll manipulate the messages to wrap the relevant words in HTML spans.

Remember that when we're loading a chat instance, the items are not only dumped into the HTML result by rendering the chatroom template, listItem.scala.html, but they are also serialized on the WebSocket during the use of the chat, that is, in the chatroom.coffee file. Therefore, here is what we'll do. We'll take the message text out and preprocess it to find words starting with @ or #.

Integrating chatrum with Twitter search

The previous screenshot shows the new listItem.scala.html template that splits the item's message into words and then processes them all, based on their first character. Note that we have also added a dedicated class for each type: mention and tag.

Note

Look at how the first character of a string can be accessed using parenthesis and the index. In Scala, a string is viewed as a sequence of characters, so we can use the access method of the sequence.

That was the easy part; the server side using Scala. Now let's see how to do it in CoffeeScript:

Integrating chatrum with Twitter search

It might seem far more difficult, but it's not; we've just defined several functions that have clean and clear responsibilities, such as formatting a word based on the type and the class to add. We could have also pushed the limit further by adding the wrapping element's tag name to the argument list.

Indeed, we're essentially doing the same thing as in listItem.scala.html: splitting the message and formatting each word. The only real difference is the join usage; that's because the template system is doing it behind the scenes for us.

With a bit of formatting for classes mention and tag, we can get the result shown as follows:

Integrating chatrum with Twitter search

Cool but useless at this stage; we need to add some interaction to it. As it's pure CoffeeScript that uses everything we've seen so far, we'll just see what the result could be:

Integrating chatrum with Twitter search

This was done using only the JavaScript router to hit the actions mentioning and searchTag and a bit of jQuery to provide a panel where tweets are shown. This happens when one of the spans is clicked. For more, you can refer to the code files of the book for an example.

So far so good; we have achieved an easy and straightforward use of a third-party service such as Twitter with no pain, no response parsing, request handling, and so on.

But there is still an enormous problem: we've lost the non-blocking features that Play! Framework 2 brings. That's because we were waiting for the Promise object to return before continuing (remember the return promisedResult.get(); instruction?).

However, as mentioned earlier, that's our fault. We didn't use the WS API as recommended, and that's the point of discussion for the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset