social Creating a Facebook app with Google App Engine and Google Web Toolkit
One of the challenges with writing a presumably successful Facebook application is taking care of scale. with an ever growing user base and the high viral growth potential brought by this social platform you could be looking at a very high traffic if your application is successful. It is wise to plan ahead. Integrating a service like Google App Engine or Amazon AWS could do the trick. Especially for small and medium enterprises which can find the cost model beneficial for their first baby steps.

This post is not going to dwell on the details of creating an App Engine account or a Facebook application. Those are described very well by their providers and across the web.

The example is an application called “famousity”. It authenticates a Facebook user then looks through his friends and determines how famous they are by executing a Google search on their exact name and comparing the hit count. Not very clever but will do in order to get a first feel of these tools.

After creating a Facebook application you should be see the following screen:

FacebookAppSettings Creating a Facebook app with Google App Engine and Google Web Toolkit

As you can see the Canvas URL is http://fbfamousity.appspot.com is where our Google App Engine application resides. We’re going to need our application id, API Key and App Secret values moving forward so we keep them handy by creating a class to encapsulate them (of course in a real world scenario we should have externalized these):

public class FacebookUtil {
    private static final String API_KEY = "API_KEY"; // replace with real values from Facebook app configuration
    private static final String SECRET = "SECRET"; // replace with real values from Facebook app configuration
    private static final String APPLICATION_ID = "APPLICATION_ID "; // replace with real values from Facebook app configuration

    private static final String FB_GRAPH_URL = "https://graph.facebook.com/";
    private static final String FB_OAUTH_URL = FB_GRAPH_URL + "oauth/";
    private static final String FB_FRIENDS_URL = FB_GRAPH_URL + "me/friends?";

    // private static final String REDIRECT_URL =
    // "http://apps.facebook.com/famousity/";

    // localhost is for testing
    private static final String REDIRECT_URL = "http://localhost:8888/";

    public static String getApplicationId() {
        return APPLICATION_ID;
    }

    public static String getAPIKey() {
        return API_KEY;
    }

    public static String getSecret() {
        return SECRET;
    }

    public static String getAuthorizeUrl() {
        final StringBuilder sb = new StringBuilder(FB_OAUTH_URL);
        sb.append("authorize?client_id=").append(APPLICATION_ID);
        sb.append("&display=page&redirect_uri=").append(REDIRECT_URL);
        sb.append("&scope=user_status,publish_stream,offline_access,email");
        return sb.toString();
    }

    public static String getAccessTokenUrl(final String authCode) {
        final StringBuilder sb = new StringBuilder(FB_OAUTH_URL);
        sb.append("access_token?canvas=1&fbconnect=0&type=user_agent&client_id=").append(
                APPLICATION_ID);
        sb.append("&redirect_uri=").append(REDIRECT_URL);
        sb.append("&client_secret=").append(SECRET);
        sb.append("&code=").append(authCode);
        return sb.toString();
    }

    public static String getFriendsUrl(final String authToken) {
        return FB_FRIENDS_URL + authToken;
    }
}

One thing to note here about this class is that the REDIRECT_URI field has a value of either http://localhost:8888/ or http://apps.facebook.com/famousity/ depending on weather we are working in our local development environment or the App Engine “production” environment. Remember to change this before deploying your application to App Engine (the change needs to take place in the Facebook application settings as well).

For my development environment I used STS with Google Integration. However, if you use Eclipse you can simply install the Google Eclipse Plugin which provides App Engine and GWT support within the IDE.

After installing the IDE plugin you get a new Google toolbar for creating a new web application, compiling GWT, profiling to speed tracer and deploying your app to App Engine. It seems that even tighter Eclipse integration is planned for the next version of the plugin.

GoogleEclipseToolbar Creating a Facebook app with Google App Engine and Google Web Toolkit


Facebook’s authentication is composed of several steps:

The user initially goes to http://apps.facebook.com/famousity the canvas page is invoked. After that ourapplication needs to send an authorization request with the appropriate permissions. Something along the lines of:

https://graph.facebook.com/oauth/authorize?client_id=APP_CLIENT_ID&display=page&redirect_uri=http://localhost:8888/&scope=user_status,publish_stream,offline_access,email

Once the user authorizes our app it will redirect back to the parameter specified under “redirect_uri” with the access token.

This covers the initial authorization flow, but you must also account for the scenario where the user denies access to your app in which case face will provide you with an error message parameter in the form of:

http://YOUR_CANVAS_PAGE?error_reason=user_denied&error=access_denied&error_description=The+user+denied+your+request

The next time the user arrives at the application it has already been authorized but you still need to retrieve the access token. The application needs to execute access token request. It looks like:

https://graph.facebook.com/oauth/access_token?canvas=1&fbconnect=0&type=user_agent&client_id=APP_CLIENT_ID&redirect_uri=http://localhost:8888/&client_secret=APP_SECRET

After that the application can proceed to execute authorized graph API calls using the provided token:

https://graph.facebook.com/me/friends?access_token=FB_PROVIDED_ACCESS_TOKEN


To create a new GWT-App Engine-Facebook project open Eclipse and click the New Web Application Project, type the name and a package and click Finish. This will create a new GWT application. It will also generate some sample code, notice it has created a “client”, “server” and “shared” packages The client package should contain two interfaces the RemoteService specification interface and it’s Async correspondent. In our case we will create a Facebook service to handle facebook operations:

@RemoteServiceRelativePath("facebook")
public interface FacebookService extends RemoteService {
    public String login(String authToken);
    public List<FbFriend> findFriends(String authToken);
}

And it’s Async twin, an identical interface with another parameter, a typed AsyncCallback:

public interface FacebookServiceAsync {
    public void login(String authToken, AsyncCallback<String> asyncCallback);
    public void findFriends(String authToken, AsyncCallback<List<FbFriend>> asyncCallback);
}

Our Facebook service contains two methods, one is for the initial authentication of the user and accepts an existing authentication token used to pass back to the client. This lets the client know that the user was previously authenticated and can proceed into the app. For more information on the Facebook authentication flow check their documentation. The login method returns the authentication token which is later used in subsequent calls to the Facebook API.

The findFriends method accepts the authentication token and retrieves a list of FbFriend instances. Note that in GWT classes used to communicate between the client and the remote service must implement the IsSerializable interface.

public class FbFriend implements IsSerializable {

    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public void setId(final Long id) {
        this.id = id;
    }
}

The FacebookService implementation is shown below

public class FacebookServiceImpl extends RemoteServiceServlet implements FacebookService {
    private static final long serialVersionUID = 1L;

    public String login(final String authToken) {
        final String url = FacebookUtil.getAccessTokenUrl(Util.encode(authToken));
        return Util.fetchURL(url);
    }

    public List<FbFriend> findFriends(final String authToken) {
        final String friendsUrl = FacebookUtil.getFriendsUrl(encodeUrl(authToken));
        final String json = Util.fetchURL(friendsUrl);
        final FBFriends fbFriends = new Gson().fromJson(json, FBFriends.class);
        return fbFriends.getData();
    }

    private static String encodeUrl(final String unencodedToken) {
        final String[] parts = unencodedToken.split("=");
        if (parts.length < 2) {
            return "";
        }
        return "access_token=" + Util.encode(parts[1]);
    }
}

Our FacebookService will handle the login and the friend retrieval but what about the Google name search hit counts? For this we devise another service with an async counterpart:

@RemoteServiceRelativePath("google")
public interface GoogleService extends RemoteService {
    public Long findFriendRanking(FbFriend friend);
}
public interface GoogleServiceAsync {
    public void findFriendRanking(FbFriend friend, final AsyncCallback<Long> asyncCallback);
}

And here is the implementation for this service:

public class GoogleServiceImpl extends RemoteServiceServlet implements GoogleService {
    private static final long serialVersionUID = 1L;

    private static final Logger logger = LoggerFactory.getLogger(Util.class);
    private static final String GOOGLE_API_URL = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=";

    private final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

    public Long findFriendRanking(final FbFriend friend) {
        final Key key = KeyFactory.createKey("profile", friend.getId());
        try {
            final Entity profile = datastore.get(key);
            final Object result = profile.getProperty("estimatedResultCount");
            return (Long) result;
        } catch (final EntityNotFoundException e) {
            logger.debug("Could not find an entry for {} in the datastore.", friend.getId());
            final Long resultCount = findResultCount(friend.getName());
            final Entity newProfile = new Entity(key);
            newProfile.setProperty("profileName", friend.getName());
            newProfile.setProperty("estimatedResultCount", resultCount);
            datastore.put(newProfile);
            return resultCount;
        }
    }

    public Long findResultCount(final String name) {
        final String queryUrl = GOOGLE_API_URL + Util.encode("\"" + name + "\"");
        logger.debug("Executing google query {}", queryUrl);
        final String resultJson = Util.fetchURL(queryUrl);

        final ResponseDataContainer container = new Gson().fromJson(resultJson,
                ResponseDataContainer.class);

        if (null != container && null != container.getResponseData()
                && null != container.getResponseData().getCursor()) {
            final Long resultCount = container.getResponseData().getCursor()
                    .getEstimatedResultCount();
            return resultCount;
        } else {
            return -1L;
        }
    }
}

We create a key based on the friend’s Facebook id and then use that key to search the AppEngine datastore. If the key doesn’t exist we handle the EntityNotFoundException by executing a new search and placing the result values in the datastore for future use.

When working in the local development environment, notice that the AppEngine library has created a local datastore for you. This can be found under “war/WEB-INF/appengine-generated/local_db.bin” in our workspace.

In case you’re wondering about the validity of the results here it is a complete spoof. The Google Ajax Search API has been deprecated for quite some time now and the correctness of the results is dubious at best. It will do for this example but not for any real world apps.


Now to the GWT client code, here we need to perform a few basic steps:

We use the returned “code” parameter returned to us by Facebook to see if the user has an access token. If he doesn’t we redirect him to the authorization Url this will cause Facebook to display the following authorization dialog:

FacebookAuthorizeFamousity Creating a Facebook app with Google App Engine and Google Web Toolkit

Next we check for the “user_denied” parameter place by the caller (Facebook) and display an appropriate error message to the user (if we want to force him to authorize all permissions, depending on what our app requires to function). If the user has denied our authorization request we better notify him that he needs to authorize us:

FamousityDeny Creating a Facebook app with Google App Engine and Google Web Toolkit

After we’re done with the authorization checks we can now safely proceed with our access token to retrieve the user’s friend list, using our FacebookService remote interface. For each friend returned we draw a widget then execute a name search using our GoogleService remote interface.

public class Famousity implements EntryPoint {

    private final FacebookServiceAsync facebookService = GWT.create(FacebookService.class);
    private final GoogleServiceAsync googleService = GWT.create(GoogleService.class);

    private MessageBox messageBox;

    public void onModuleLoad() {
        final String authToken = Location.getParameter("code");
        final String error = Location.getParameter("error_reason");

        if (null != error && error.equals("user_denied")) {
            userDenied();
        } else if (authToken == null || "".equals(authToken)) {
            redirect(FacebookUtil.getAuthorizeUrl());
        } else {
            messageBox = MessageBox.wait("Famousity", "", "Analyzing friend rankings...");

            facebookService.login(authToken, new AsyncCallback<String>() {
                public void onFailure(final Throwable caught) {
                    handleError(caught);
                }

                public void onSuccess(final String authToken) {
                    rankFriends(authToken);
                }
            });
        }
    }

    private void userDenied() {
        MessageBox.confirm("Error authrizing famousity",
                "You have deined famousity from installing correctly, click 'Yes' to try again",
                new Listener<MessageBoxEvent>() {

                    public void handleEvent(final MessageBoxEvent be) {
                        if (be.getButtonClicked().getText().equals("Yes")) {
                            redirect(FacebookUtil.getAuthorizeUrl());
                        } else {
                            be.getMessageBox().close();
                        }
                    }
                });
    }

    private void rankFriends(final String authToken) {

        final RootPanel rootPanel = RootPanel.get();
        facebookService.findFriends(authToken, new AsyncCallback<List<FbFriend>>() {
            public void onFailure(final Throwable caught) {
                Window.alert(caught.getMessage());
            }

            public void onSuccess(final List<FbFriend> friends) {
                closeMessageBox();
                final Header header = new Header();
                header.setText("How famous are your friends");
                rootPanel.add(header);

                for (final FbFriend friend : friends) {
                    final Panel friendPanel = new HorizontalPanel();

                    friendPanel.setStyleName("panel");
                    final Image profilePic = new Image(getProfilePictureUrl(friend));
                    profilePic.setStyleName("profilePic");
                    friendPanel.add(profilePic);
                    friendPanel.add(new Hidden(friend.getId().toString()));
                    friendPanel.add(new Text(friend.getName()));
                    final Text countText = new Text("");
                    friendPanel.add(countText);
                    rootPanel.add(friendPanel);

                    googleService.findFriendRanking(friend, new AsyncCallback<Long>() {

                        public void onFailure(final Throwable caught) {
                            handleError(caught);
                        }

                        public void onSuccess(final Long result) {
                            if (result > 0) {
                                countText.setText(result.toString());
                            } else {
                                countText.getParent().setVisible(false);
                            }
                        }
                    });
                }
            }

        });

    }

    private void handleError(final Throwable caught) {
        Window.alert(caught.getMessage());
    }

    private String getProfilePictureUrl(final FbFriend friend) {
        return "http://graph.facebook.com/" + friend.getId() + "/picture";
    }

    private void closeMessageBox() {
        if (messageBox != null) {
            messageBox.close();
            messageBox = null;
        }
    }

    // redirect the browser to the given url
    public static native void redirect(String url)/*-{
		$wnd.location = url;
    }-*/;
}

While the user is waiting a neat progress bar is displayed using the aid of GWT-EXT:

FamousityProgress Creating a Facebook app with Google App Engine and Google Web Toolkit

This is what the (censored) output looks like:

FanmousityRankTable Creating a Facebook app with Google App Engine and Google Web Toolkit

A few words about the GWT code here. GWT relies heavily on asynchronous calls. There is some suspicion that rises from nesting these calls although Google has a good explanation for why this is the way it is. Some discussion of this issue can be found here and here. It seems some recommend moving logic to the server while other try to devise more neat solutions for the code. In AppEngine more logic running on the server may pose a problem due to the 30 second limitation. We must take these matters under consideration when combining these two technologies. The need to explicitly implement the RemoteService and it’s Async twin seem a bit off as well, they smell a little like the old EJB stuff. Other MVC implementations save you this kind of boilerplate code by using annotations or configuration files.

After testing the application in Eclipse, click the “Deploy App Engine Project” button in Eclipse then provide the Google credentials and our application get’s compiled and deployed automatically. A pretty cool combination, for my next social project I think I am going to take a closer look at Spring Social.

Source code is available here.