all that jazz

james' blog about scala and all that jazz

Facebook authentication in Java

If you're a web developer who likes writing practical, quick, and simple utility applications for yourself and others to use, then Facebook is the dream platform. You don't have to write any user management, sign in, password change pages etc, it comes complete with advanced user management, including friends lists. There's a high chance that your target users already use it, which makes them more likely to adopt your application because they don't need to go and create yet another username/password on yet another website. It already has a theme and style sheets, and will present your application surrounded in menus and title bars. And it provides a large number of ways to publish content and send notifications when events occur. All these features mean that you, the developer, can spend more time doing what you wanted to do, that is, writing your application.

If Java is your chosen platform however, you may encounter some difficulties. Java developers tend to like following well established design patterns. We like to think of things as beans that have distinct purposes. We like to use frameworks such as Spring to manage the beans that contain logic, to glue our application together. We like our data to be stored in beans, and we access that data using getters and setters. The Facebook client API makes this difficult, for the following reasons:

  1. Its instantiation is dependent on parameters in an HTTP request. This means we can't use it as a typical Spring bean.
  2. Depending on which flavour you of client you use, it returns JSON objects, or DOM documents, not the Java beans we like to use.

This article will address the first issue, and in doing so make the second issue less of a worry.

Instantiation of the client

Our first task is to gracefully handle the instantiation of the Facebook client. This will usually be required by most requests, hence, it is an ideal opportunity to use a ServletFilter. The guys at TheLiveWeb have already provided a very good article on how to do this, so I won't go into too much detail. The servlet filter basically intercepts every request, checks to see if there is already a client in the session, and if not, uses the fb_session parameter if it's an embedded fbml app, or auth_token parameter if it's not an fbml app, to instantiate the client. If neither of those parameters exist, it redirects the user to the login page. The ServletFilter I've written is very different to theirs, but both follow the same concept and achieve the same goal.

Using a ThreadLocal to access the client

Our next task is to make the client available to the backend service beans that have no knowledge of the HTTP session. We do this using a ThreadLocal. We start by creating a wrapper class, UserClientBean, to wrap all calls to the client. The client itself is stored in a private static ThreadLocal field:

public class UserClientBean
{
    public static void setClient(FacebookXmlRestClient aClient)
    {
        client.set(aClient);
    }
    public static void remove()
    {
        client.remove();
    }
    private static final ThreadLocal client
        = new ThreadLocal();
    ...
}

So calling UserClientBean.set() will bind a facebook client to the current thread, and UserClientBean.remove() will remove it. We make these calls from the servlet filter:

public void doFilter(ServletRequest aRequest, ServletResponse aResponse,
        FilterChain aChain) throws IOException, ServletException
{
    if (aRequest instanceof HttpServletRequest)
    {
        HttpServletRequest request = (HttpServletRequest) aRequest;
        HttpServletResponse response = (HttpServletResponse) aResponse;
        HttpSession session = request.getSession();
        FacebookXmlRestClient client = (FacebookXmlRestClient) session
                    .getAttribute("facebookClient");
        if (client == null)
        {
            String sessionKey = request
                    .getParameter(FacebookParam.SESSION_KEY.toString());
            String authToken = request.getParameter("auth_token");
            if (sessionKey == null && authToken == null)
            {
                response.sendRedirect(loginPage);
                return;
            }
            else
            {
                try
                {
                    if (sessionKey != null)
                    {
                        client = new FacebookXmlRestClient(apiKey, secret,
                                sessionKey);
                    }
                    else
                    {
                        client = new FacebookXmlRestClient(apiKey, secret);
                        client.auth_getSession(authToken);
                    }
                    client.setIsDesktop(false);
                    session.setAttribute("facebookClient", client);
                }
                catch (FacebookException fe)
                {
                    log.error("Unable to log into facebook", fe);
                }
            }
        }
        // Store the client in the thread
        UserClientBean.setClient(client);
    }
    aChain.doFilter(aRequest, aResponse);
    // Remove the client from the thread
    UserClientBean.remove();
}

If you've never come across ThreadLocal's before, a ThreadLocal is a class that binds the object passed to its set() method to the current thread. Calling the get() method will retrieve that object from the current thread, if it exists. ThreadLocal's are usually declared as static final, so that they can be accessed statically from anywhere in the application.

Using the client

Now that we can access the client as a service, we can add calls as desired to UserClientBean to run on the FaceBookRestClient, and extract the data out in a way that's nice to return to our application. For example:

public List getAppFriends()
{
    List result = new ArrayList();
    try
    {
        Document d = client.get().friends_getAppUsers();
        Node n = d.getFirstChild();
        while (n != null)
        {
            result.add(Long.parseLong(n.getTextContent()));
            n = n.getNextSibling();
        }
    }
    catch (Exception e)
    {
        log.error("Error retrieving logged in facebook user", e);
    }
    return result;
}

public boolean isFriend(long aFriendId)
{
    try
    {
        FacebookXmlRestClient c = client.get();
        long user = c.users_getLoggedInUser();
        if (user == aFriendId)
        {
            return true;
        }
        else
        {
            Document d = c.friends_areFriends((int) user, (int) aFriendId);
            Node n = d.getElementsByTagName("are_friends").item(0);
            if (n.getTextContent().equals("1"))
            {
                return true;
            }
        }
        return false;
    }
    catch (Exception e)
    {
        log.error("Error determining if users are friends", e);
        return false;
    }
}

As you may have noticed, the FaceBookRestClient uses ints, while Facebook recommends user ids being stored as longs. Not very smart huh? At least using this client, our apps will be ready for that bug fix.

A new blog

Welcome to my new blog. This blog is going to contain everything from personal posts about my life (which probably won't interest many people) to technical articles about Java, and related Java frameworks.

Back to Bangkok

My trip is now starting to come to an end. After my last blog entry, I spent a night in Mai Sai, took a bus to Chiang Rai, and then stayed there the night. Chiang Rai was kinda boring, I got a 2 hour massage, and then wandered around for a while. The night markets were good though, I bought a few last pressies for various people. One big thing I've noticed is the further north you go in Thailand, the less and less English you see. In Bangkok, everything's in English and Thai. But, there were hardly any English signs in Chiang Rai or Mai Sai. There is also a much larger population of Chinese in the north, a lot of the markets have chinese food in them, and a lot of the restaurants, particularly the more western ones, are owned by Chinese.

The next day we took the bus to Chiang Mai, where I had one last walk around, had lunch, and sat in a cafe reading the paper, before we took the overnight train to Bangkok. There were hardly any people on the train, compared to our first train ride which was full. So, there was nothing really much to do and I got an early night at 7pm. The train arrived in Bangkok at 6am, where I took a taxi back to the Khao San Road, and checked into a hotel. So now I'm here, and I'm waiting for everything to open before I go out and explore. I think I'll take a river boat ride again, seeing as I missed out on my last one.

Tomorrow I'll be departing from the new international airport in Bangkok. It is said to have the largest terminal in the world, and once all its extensions are finished, will be the biggest airport in the world, handling 100 million passengers a year. There have been big problems with its opening 2 days ago though. People were waiting for hours for their baggage, and apparently 200 items got sent to the wrong destination. The problem was due to the fact that they didn't transfer enough baggage wagons from the old airport to the new. There were also computer problems for Thai Airways, but seeing as I'm traveling with British Airways, that won't be a problem.

Haven't seen any tanks, or any soldiers for that matter, since coming into Bangkok. Maybe, just as the coup appeared overnight when I was on the train to Chiang May, disappeared overnight on the way to Bangkok. Or maybe it just never happened. Hopefully I can find some tanks today to have my picture taken with.

Thai Cooking

One of the best experiences I've had so far on this trip was doing a Thai cooking course. No one from my tour wanted to do it, instead I ended up doing it with an Intrepid group, there were 6 of us in all. We started off by going to the markets and buying most of the ingredients needed for what we were cooking. Our instructor explained to us what the different spices were, and showed us different vegies, and taught us how to recognise which ones were good and which weren't. We also saw them make coconut cream and coconut milk fresh for us.

At the cooking course, I made pad thai, thai spicy soup, green curry, and banana in coconut milk. The food was amazing, best Thai food I've had in Thailand! It was also incredibly quick to cook, I never realised it was so quick to make Thai food properly. When I get back to Australia I am definitely seeking out all the ingredients to make the food, it's almost as quick as unfreezing the frozen meals I usually make.

Chiang Rai and the Golden Triangle

How does deep friend silk worms, bamboo worms and crickets sound? Well, last night I tried them all. for about 60c I got a platter of them with chilli sauce, mmm.... The taste was ok, but the texture… A taste was enough, I went and got some prawn cutlets, spring rolls and basil chicken. That was last night in Chiang Rai.

Today we took a local bus to the Golden Triangle. The Golden Triangle is the intersection between the borders of Thailand, Laos and Burma. I'm told it used to be the opium capital of South East Asia. The Laos border is defined by the Mekong River. I was amazed at just how much water is flowing down this river. It is wider than Lake Burley Griffen, looks quite deep, but it's flowing at quite a rapid pace, too fast to swim against. It flows down from the Himalayas.

We crossed the Mekong and landed at Laos, though, it was only a large island on the river, no border control or anything. Nevertheless, there was a Laos post box there, and I sent a postcard to my parents from there, therefore I was in Laos. Something weird there was that the people put snakes - king cobras, and scorpians, in their whiskey. There were all these whiskey bottles with snakes and scorpians in them. I didn't try it.

After that we went to Mae Sai, the northern most point of Thailand. It is here that we crossed into Burma. This time, we did have to officially leave Thailand, pay $13 for a visa into Burma, and then get a new visa when we came back into Thailand. Burma was a huge contrast to Thailand. The streets were covered in mud, I almost slipped over it was that muddy. In the worst parts, the mud had been scooped up into piles on the roads, and drivers had to weave between the piles. The people were very poor, there were a lot of beggars, particularly young children, much more than in Thailand. In Thailand, there are little food stalls everywhere, in Burma, there were hardly any, and they were much more dirty and run down.

We went shopping in the markets, the people were much more persistent in trying to sell us things, particularly cigarettes, porn movies and viagra. They'd follow you all the way down the street, not like Thailand at all. Coming back into Thailand, I was amazed at how clean it was. Previously I had been thinking Thailand was quite dirty, but now that I'm back here, I look around and it's so clean. It just goes to show the cleanliness of a place is all relative.

About

Hi! My name is James Roper, and I am a software developer with a particular interest in open source development and trying new things. I program in Scala, Java, Go, PHP, Python and Javascript, and I work for Lightbend as the architect of Kalix. I also have a full life outside the world of IT, enjoy playing a variety of musical instruments and sports, and currently I live in Canberra.