<img height="1" width="1" style="display:none;" alt="" src="https://px.ads.linkedin.com/collect/?pid=299788&amp;fmt=gif">

Developing a Facebook application in less than a six pack - Part Two

Software Development, Spring, Software Solutions

By Joel Brinkman

We finished up our last post having a rather lame Facebook application. Now we'll use RestFB to do something more interesting for our users.

Making it real

When Facebook posts to our application, it tacks on a request parameter named signed_request, the value of which contains the information required to identify the user as well as the token with which we will make calls to the Facebook API. Before this information is useable we must first decrypt the contents. The methods below should be added to our controller.


[java]
private FacebookRequest signedRequestToFacebookRequest(String signedRequest) {
if (signedRequest == null || signedRequest.isEmpty()) {
return null;
}
String json = gimmieSomeJson(signedRequest);
FacebookRequest answer = new Gson().fromJson(json, FacebookRequest.class);
return answer;
}
private String gimmieSomeJson(String encoded) {
int idx = encoded.indexOf(&amp;quot;.&amp;quot;);
byte[] sig = new Base64(true).decode(encoded.substring(0, idx).getBytes());
String rawpayload = encoded.substring(idx+1);
String payload = new String(new Base64(true).decode(rawpayload));
checkSignature(rawpayload, sig);
return payload;
}
private void checkSignature(String rawpayload, byte[] sig) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), SIGN_ALGORITHM);
Mac mac = Mac.getInstance(SIGN_ALGORITHM);
mac.init(secretKeySpec);
byte[] mysig = mac.doFinal(rawpayload.getBytes());
if (!Arrays.equals(mysig, sig)) {
throw new RuntimeException(&amp;quot;Non-matching signature for request&amp;quot;);
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(&amp;quot;Unknown hash algorithm &amp;quot; + SIGN_ALGORITHM, e);
} catch (InvalidKeyException e) {
throw new RuntimeException(&amp;quot;Wrong key for &amp;quot; + SIGN_ALGORITHM, e);
}
}
[/java]
Note the code above references a new type: FacebookRequest. Let's add this new class to the project.
[java]
@RooJavaBean
public class FacebookRequest {
private String algorithm;
private String expires;
private String issued_at;
private String oauth_token;
private String user_id;
}
[/java]
There are a few other items referenced in the code above that we must now provide. Add the following constants to your controller substituting your values for mine.
[java]
private static final String secret = &amp;quot;somesecret&amp;quot;;
private static final String appId = &amp;quot;yourappidgoeshere&amp;quot;;
private static final String canvasPage = &amp;quot;http://apps.facebook.com/yourappidgoeshere/&amp;quot;;
private static final String SIGN_ALGORITHM = &amp;quot;HMACSHA256&amp;quot;;
[/java]
Now, to get the signed_request value in our controller, modify our method signature:
[java]
public ModelAndView post(@RequestParam(value=&amp;quot;signed_request&amp;quot;) String signedRequest, HttpSession session)[/java]
Once the signed request is in hand, we can pray our new method signedRequestToFacebookRequest() might help us to make sense out of the madness. When the application is accessed by a user that has not explicitly granted permission to our application, the FacebookRequest will contain a null user_id.
If the user has not blessed us with permission to access their Facebook data, we must first beg for it. This can be done by redirecting the user back to the Facebook site on a specific URL. We will do this with JavaScript. If the user_id turns out to be null, we will render a view that contains the JavaScript required to push them back to the page where they will be begged for permission.
Let's add a new JSP and a new Apache Tiles definition.
Add the tiles def for the permission page in /WEB-INF/views/views.xml:
[xml]
&amp;lt;definition extends=&amp;quot;public&amp;quot; name=&amp;quot;permission&amp;quot;&amp;gt;
&amp;lt;put-attribute name=&amp;quot;body&amp;quot; value=&amp;quot;/WEB-INF/views/permission.jspx&amp;quot;/&amp;gt;
&amp;lt;/definition&amp;gt;
[/xml]
Add permission.jspx to /WEB-INF/views/. We need to redirect on the client because the way our canvas application is rendered makes a sendRedirect impossible.
[xml]
&amp;lt;div xmlns:jsp=&amp;quot;http://java.sun.com/JSP/Page&amp;quot; xmlns:spring=&amp;quot;http://www.springframework.org/tags&amp;quot; xmlns:util=&amp;quot;urn:jsptagdir:/WEB-INF/tags/util&amp;quot; version=&amp;quot;2.0&amp;quot;&amp;gt;
&amp;lt;jsp:directive.page contentType=&amp;quot;text/html;charset=UTF-8&amp;quot;/&amp;gt;
&amp;lt;jsp:output omit-xml-declaration=&amp;quot;yes&amp;quot;/&amp;gt;
&amp;lt;script&amp;gt;&amp;lt;![CDATA[top.location.href='https://www.facebook.com/dialog/oauth?client_id=${appId}&amp;amp;redirect_uri=${canvasPage}&amp;amp;scope=read_friendlists']]&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/div&amp;gt;
[/xml]
Now, modify the controller such that when the user_id is blank, send this new view.
[java]
@RequestMapping(method=RequestMethod.POST)
public ModelAndView post(@RequestParam(value=&amp;quot;signed_request&amp;quot;) String signedRequest, HttpSession session) {
FacebookRequest facebookRequest = signedRequestToFacebookRequest(signedRequest);
ModelAndView answer = new ModelAndView(&amp;quot;trend/index&amp;quot;);
if (facebookRequest.getUser_id() == null || facebookRequest.getUser_id().isEmpty()) {
//there is no user information in the signedRequest. render the view that redirects to Facebook permission page
answer = new ModelAndView(&amp;quot;permission&amp;quot;);
answer.addObject(&amp;quot;appId&amp;quot;, appId);
answer.addObject(&amp;quot;canvasPage&amp;quot;, canvasPage);
} else {
//it seems we know the userId
answer = new ModelAndView(&amp;quot;trend/index&amp;quot;);
}
return answer;
}
[/java]


Now start the application and navigate to the app URL. The user will be redirected to the Facebook permission page. It should look like this:
enterprise application development
Click Ok. Eventually, you'll be redirected back to the application. We have now authorized our canvas application to access your Facebook data.
Now let's do something a little more interesting with technological terror we've created. Once the user has granted us permission to access their Facebook data, the application will be able to get a user_id and the oauth_token from the signed_request and with this, make calls to the Facebook API. Each time a user accesses this application, we will grab the current friend count and store the history of this number in a cookie (as we are too lazy to setup a database for this right now).


We will use the RestFB library to get a list of the user's friends like this:


[java]
FacebookClient facebookClient = new DefaultFacebookClient(facebookRequest.getOauth_token())
Connection&amp;lt;User&amp;gt; myFriends = facebookClient.fetchConnection(&amp;quot;me/friends&amp;quot;, User.class);
[/java]


In context, it looks something like this. Replace your controller method with this one.

[java]@RequestMapping(method = RequestMethod.POST)
public ModelAndView post(@CookieValue(value=&amp;quot;friendHistory&amp;quot;, required=false) String friendHistory, @RequestParam(value = &amp;quot;signed_request&amp;quot;) String signedRequest, HttpSession session, HttpServletResponse response) {
FacebookRequest facebookRequest = signedRequestToFacebookRequest(signedRequest);
ModelAndView answer = new ModelAndView(&amp;quot;trend/index&amp;quot;);
if (facebookRequest.getUser_id() == null || facebookRequest.getUser_id().isEmpty()) {
// there is no user information in the signedRequest. render the
// view that redirects to Facebook permission page
answer = new ModelAndView(&amp;quot;permission&amp;quot;);
answer.addObject(&amp;quot;appId&amp;quot;, appId);
answer.addObject(&amp;quot;canvasPage&amp;quot;, canvasPage);
} else {
// it seems we know the userId. Let's get the list of friends.
FacebookClient facebookClient = new DefaultFacebookClient(facebookRequest.getOauth_token());
Connection&amp;lt;User&amp;gt; myFriends = facebookClient.fetchConnection(&amp;quot;me/friends&amp;quot;, User.class);
// if there is no history in the cookie or if the cookie is null,
// shove the friend count in there.
friendHistory = friendHistory == null || friendHistory.isEmpty() ? String.valueOf(myFriends.getData().size()) : friendHistory;
// let's convert our friendHistory value in a list of integers
List&amp;lt;String&amp;gt; friendHistoryValues = new ArrayList&amp;lt;String&amp;gt;(Arrays.asList(friendHistory.split(&amp;quot;,&amp;quot;)));
// if the last value in our friend count history is different than
// the current value, add the current value to the end.
Integer lastHistoricalValue = null;
switch (friendHistoryValues.size()) {
case 0: {
lastHistoricalValue = friendHistoryValues.size();
break;
}
case 1: {
lastHistoricalValue = Integer.valueOf(friendHistoryValues.get(friendHistoryValues.size() - 1));
break;
}
default: {
lastHistoricalValue = Integer.valueOf(friendHistoryValues.get(friendHistoryValues.size() - 2));
}
}
Integer currentFriendCount = myFriends.getData().size();
if (friendHistoryValues.size() == 0 || !lastHistoricalValue.equals(currentFriendCount)) {
// since the current count differs from the last saved count,
// jam it in the list.
friendHistoryValues.add(String.valueOf(currentFriendCount));
}
if (friendHistoryValues.size() &amp;gt; 1) {
// if we have more than one item in our list, see if we last
// went up or down in our friend count
answer.addObject(&amp;quot;lastHistoricalValue&amp;quot;, lastHistoricalValue);
answer.addObject(&amp;quot;currentFriendCount&amp;quot;, currentFriendCount);
if (lastHistoricalValue &amp;gt; currentFriendCount) {
answer.addObject(&amp;quot;direction&amp;quot;, &amp;quot;down&amp;quot;);
} else {
answer.addObject(&amp;quot;direction&amp;quot;, &amp;quot;up&amp;quot;);
}
} else {
answer.addObject(&amp;quot;direction&amp;quot;, &amp;quot;static&amp;quot;);
}
// determine if we went up or down in the friend count
// jam our values into the cookie
String cookieFilling = &amp;quot;&amp;quot;;
for (String moo : friendHistoryValues) {
cookieFilling += moo + &amp;quot;,&amp;quot;;
}
cookieFilling = cookieFilling.substring(0, cookieFilling.length() - 1);
response.addCookie(new Cookie(&amp;quot;friendHistory&amp;quot;, cookieFilling));
}
return answer;
}
[/java]

Going pro - buttoning up the user interface

Our fun little application consists of just one page. By now you should have noticed that our user interface is pretty ugly; moreover, it contains elements we simply do not want. The first step in banishing this eyesore is creating a new Tiles layout. Find the layouts.xml file and add a new entry:


[xml]
&amp;lt;definition name=&amp;quot;simple&amp;quot; template=&amp;quot;/WEB-INF/layouts/default.jspx&amp;quot; /&amp;gt;
[/xml]


Next, change the tile definition for trend/index such that it extends simple. This definition can be found in /src/main/webapp/WEB-INF/views/trend/views.xml. It should look like this when you're done:

[xml]
&amp;lt;definition extends=&amp;quot;simple&amp;quot; name=&amp;quot;trend/index&amp;quot;&amp;gt;
&amp;lt;put-attribute name=&amp;quot;body&amp;quot; value=&amp;quot;/WEB-INF/views/trend/index.jspx&amp;quot;/&amp;gt;
&amp;lt;/definition&amp;gt;
[/xml]


Restart the application and visit the app URL. Your view should now look more like this:
enterprise application development
Our application is starting to look like something real. There is but one last step to make it functional and on its way to being your favorite Facebook app since Cow Clicker. Modify trend/index.jspx to actually display something interesting:


[xml]
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;
&amp;lt;div xmlns:c=&amp;quot;http://java.sun.com/jsp/jstl/core&amp;quot; xmlns:jsp=&amp;quot;http://java.sun.com/JSP/Page&amp;quot; xmlns:spring=&amp;quot;http://www.springframework.org/tags&amp;quot; xmlns:util=&amp;quot;urn:jsptagdir:/WEB-INF/tags/util&amp;quot; version=&amp;quot;2.0&amp;quot;&amp;gt;
&amp;lt;jsp:directive.page contentType=&amp;quot;text/html;charset=UTF-8&amp;quot;/&amp;gt;
&amp;lt;jsp:output omit-xml-declaration=&amp;quot;yes&amp;quot;/&amp;gt;
&amp;lt;c:choose&amp;gt;
&amp;lt;c:when test=&amp;quot;${direction eq 'static'}&amp;quot;&amp;gt;
&amp;lt;img style=&amp;quot;vertical-align:middle&amp;quot; src=&amp;quot;http://cdn1.iconfinder.com/data/icons/orb/128/2.png&amp;quot; /&amp;gt;
It looks like you have no history of friend count changes. Do the right thing, unfriend someone.
&amp;lt;/c:when&amp;gt;
&amp;lt;c:when test=&amp;quot;${direction eq 'up'}&amp;quot;&amp;gt;
&amp;lt;img style=&amp;quot;vertical-align:middle&amp;quot; src=&amp;quot;http://cdn1.iconfinder.com/data/icons/orb/128/9.png&amp;quot; /&amp;gt;
You friend list is growing. You had ${lastHistoricalValue} friends and now ${currentFriendCount}.
&amp;lt;/c:when&amp;gt;
&amp;lt;c:when test=&amp;quot;${direction eq 'down'}&amp;quot;&amp;gt;
&amp;lt;img style=&amp;quot;vertical-align:middle&amp;quot; src=&amp;quot;http://cdn1.iconfinder.com/data/icons/orb/128/10.png&amp;quot; /&amp;gt;


Nice work! You're pruning your friend list. You had ${lastHistoricalValue} friends and now

${currentFriendCount}.
&amp;lt;/c:when&amp;gt;
&amp;lt;/c:choose&amp;gt;
&amp;lt;/div&amp;gt;
[/xml]
Start up the application and visit the app URL. You should see something like this:
enterprise application development
Remove one of your loser friends then revisit the application. You'll see some pleasant news:
enterprise application development
You now have a functional Facebook canvas application. Have fun!

TAGS: Software Development, Spring, Software Solutions

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Subscribe to Our Newsletter

Recent Blog Posts