Carlos Ray and Spring’s RestTemplate

Normally I prefer causing trouble to getting in trouble, but this time the temptation is just too great.

In my last blog post, I described how I made an Android app that was a front-end on the ICNDB web site, the Internet Chuck Norris Database, only to receive a take-down email from Patton Boggs, LLP, the attorneys for Carlos Ray “Chuck” Norris. Since all I was doing was consuming JSON data from a public web site and not charging anything, I ignored the notice, only to have Google Play suspend my app shortly thereafter.

Normally my response to that would be to just let it go. I really don’t like getting into trouble. I checked out Mr. Norris on Wikipedia, however, and discovered:

  • The star of Lone Wolf McQuade is actually 73 years old (though I have no doubt he could still kick my a**).
  • He is supposedly worth $70 million dollars, making it hard for me to imagine that my little free app (which again is only consuming publicly-available data) “severely harms [him] and jeopardizes his existing business relationships”.

So, despite the fact that he’s fabulously wealthy and that he’s still a martial arts master, both of which mean he could swat me like a fly without even trying, I couldn’t resist rising to the challenge.

If you search the Google Play store for the words “Carlos Ray”, you will find an app called Internet Carlos Ray DB. I know it has at least four installs, because at the last No Fluff, Just Stuff event in Minneapolis, I installed it on two devices and a friend I was hanging out with at the hotel bar also installed it on two devices. The app also has a five-star rating, which I added myself.

You’ll also see that I even added the lawyer’s names to the main icon:
ICRDB
Talk about waving a red flag in front of a bull. When I inevitably get gored, I’ll be sure to blog about it.

When you create an Android app, you have to select a package name for it, and that name has to be unique among all the apps you deploy to Google Play. If you check out the code in my GitHub repository, you’ll see that on the “carlosray” branch I changed the package to com.kousenit.icrdb (ICRDB == Internet Carlos Ray Database, of course).

In order to discuss something technical that might actually be useful to any readers, let me address how I retrieved the JSON data and parsed it. The Android API has both some enhanced HTTP classes (similar to those from regular Java’s java.net package) and a version of Apache’s HTTP Client package. In addition, the API includes an org.json package for working with JSON data. It includes classes like JSONArray, JSONObject, and JSONTokenizer.

All of the above provide a reasonable way to download and parse JSON data, but I have a different recommendation.

From its name, the Spring Android project sounds like a way to embed a Spring container inside an Android app. While that’s actually a rather appealing idea, that’s not what the project contains. If you look at the reference guide, you’ll see the project includes an authorization module, which I didn’t need, and a REST template, which rocks.

The best part is that if you include the Jackson JSON parser jar files in your application, the REST template will automatically convert JSON data to classes and back.

Using it is easy. It’s easier, in fact, to show the code than to explain it first, so here are some snippets from my main activity class:

public class MainActivity extends Activity {
    private Button jokeButton;
    private TextView jokeView;

    // "true" ctor arg --> add default message converters
    private RestTemplate template = new RestTemplate(true);

    private static final String URL = 
       "http://api.icndb.com/jokes/random?limitTo=[nerdy]" +
               "&firstName=Carlos&lastName=Ray";
// ...
}

I have a button to get the next joke and I have a label (which for some odd reason Android calls a TextView) to display it. I also added the RestTemplate as an attribute of the class. By instantiating it with the constructor arg “true“, it loads the default message converters.

(Those are the same message converters that Spring MVC uses for its RequestBody and ResponseBody processing, by the way, which is how I learned about them.)

The URL points to the ICNDB web site, and I’ve limited the jokes to just the nerdy ones and changed the first and last name of the hero to Carlos Ray, as you can see.

Here’s my onCreate method, which looks up the GUI elements and sets the on-click listener for the button:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    jokeView = (TextView) findViewById(R.id.text_view);
    jokeButton = (Button) findViewById(R.id.icndb_button);
    jokeButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new JokeTask().execute();
        }
    });
}

Downloading a joke goes over the network, which is not something you want to do on the UI thread. The recommended way to handle that is to create a subclass of android.os.AsyncTask, which contains methods to do the work and update the UI when it’s finished. My JokeTask is just such a class:

private class JokeTask extends AsyncTask<Void, Void, String> {
    @Override
    protected String doInBackground(Void... params) {
        IcndbJoke joke = template.getForObject(URL, IcndbJoke.class);
        return joke.getJoke();
    }
        
    @Override
    protected void onPostExecute(String result) {
        jokeView.setText(result);
    }
}

The three generic parameters on AsyncTask represent the parameters to the doInBackground method, the progress data type (which I’m not using here), and the return type on doInBackground, which is also the argument to the onPostExecute method. Work done inside doInBackground is automatically off the UI thread, and then code in onPostExecute is back on the UI thread.

The cool part is the template.getForObject method. It takes a String representing the URL and a class to populate with the result. If the JSON structure matches the class attributes, the conversion is done automatically. Therefore, all I need to do is to supply the IcndbJoke class.

According to the ICNDB site, the JSON structure is:

{ "type": "success", "value": { "id": 268, "joke": "Time waits for no man. Unless that man is Chuck Norris." } }

With that in mind, here’s my IcndbJoke class:

package com.kousenit.icrdb;

@SuppressWarnings("unused")
public class IcndbJoke {
    private String type;
    private Joke value;

    // helper method to extract the joke string from inner class    
    public String getJoke() {
        return value.getJoke();
    }
    
    public String getType() { return type;}
    public void setType(String type) { this.type = type; }
    
    public Joke getValue() { return value; }
    public void setValue(Joke value) { this.value = value; }
    
    private static class Joke {
        private int id;
        private String joke;
        private String[] categories;
        
        public int getId() { return id; }
        public void setId(int id) { this.id = id; }

        public String getJoke() { return joke; }
        public void setJoke(String joke) { this.joke = joke; }
        
        public String[] getCategories() { return categories; }
        public void setCategories(String[] categories) { this.categories = categories; }
    }
}

Other than the extra helper method used to make it easier to grab the joke string directly, this is a straight map from the JSON structure to nested classes with getters and setters.

(Oh how I wish I could use Groovy here. I could cut all this down by about 80%. Sigh.)

That’s the whole story. The getForObject method on the RestTemplate accesses the web site, downloads the JSON data, and converts it to an instance of my IcndbJoke class. Then I extract the joke string from it and update the GUI.

The only complication was that for a long time the ICNDB web site didn’t properly set the Content-Type header in the response. The RestTemplate only uses the JSON parser if the Content-Type header is set to application/json. By default the web site returned text/html, so for a time I had to convert that to a String instead of my joke class. Then I used Google’s GSON parser to convert that to my IcndbJoke class and went from there. Now that the web site sets the Content-Type header correctly, I’m able to simplify the code again. Feel free to grab it if you like.

If you like the RestTemplate, be sure to check out the documentation here and the javadocs for it here. I had to add the relevant jar files to the project, but that’s all I needed in order to use it.

Eventually I plan to migrate the whole thing over to use the Gradle plugin for Android, but that’s a post for another day.

Before I finish this post, I should mention that Making Java Groovy now has two reviews at Amazon.com. Better yet, both reviewers clearly understood what the book was trying to accomplish and they both liked it. Based on my sales so far, the Amazon sales rank has reached as high as #62,521, though I’m now back down to #131,928. (Not that I’m obsessing over the numbers or anything.) Of course, Amazon doesn’t count ebooks, nor does it report on sales from the Manning web site, which is where you have to go if you want to use one of coupon codes they periodically make available.

Though I don’t have anything Groovy-related to say this week myself, I want to mention that Bobby Warner has recorded another one of his excellent Grails videos. The subject this time is the REST improvements that come with Grails 2.3, and I highly recommend it.

On a personal note, I have to say that I’m enjoying these weekly blog posts, though now I have to stop and get some REST.

(Ha! Thought I was going to make it through without one REST joke, did you? Not a chance.)

About Kenneth Kousen
I teach software development training courses. I specialize in all areas of Java and XML, from EJB3 to web services to open source projects like Spring, Hibernate, Groovy, and Grails. Find me at Google+ on Google+ I am the author of "Making Java Groovy", a Java / Groovy integration book published by Manning in the Fall of 2013.

5 Responses to Carlos Ray and Spring’s RestTemplate

  1. Eric says:

    You mention not been able to use Groovy – does Android not allow it? I’ve done some Android development before but not in a while (& not since I’ve learned groovy).

  2. Ken Kousen says:

    Right, Groovy doesn’t run on Android, mostly because Android doesn’t provide a complete Java implementation. The missing classes need to be emulated, so that even when Groovy does run properly it’s far too slow to be useful. I really hope that changes in the future.

  3. Pingback: Serving jokes locally with Ratpack and MongoDB | Stuff I've learned recently...

  4. wilq says:

    “The only complication was that for a long time the ICNDB web site didn’t properly set the Content-Type header in the response. The RestTemplate only uses the JSON parser if the Content-Type header is set to application/json. By default the web site returned text/html, so for a time I had to convert that to a String instead of my joke class. ”

    Kenneth,
    instead of doing everything by hand its easier to create a custom json converter and register it in resttemplate. Some simple code (sorry for bad formating):

    public class TextHtmlAsJsonMessageConverter extends
    MappingJacksonHttpMessageConverter {
    public TextHtmlAsJsonMessageConverter() {
    setSupportedMediaTypes(Collections.singletonList(new MediaType(“text”, “html”, DEFAULT_CHARSET)));
    }
    }

  5. Ken Kousen says:

    I agree, that’s a good solution. Fortunately, the JSON is simple enough that almost anything worked. The hard part was realizing that the site wasn’t setting the Content-Type header.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 1,343 other followers

%d bloggers like this: