I had a really hard time implementing Facebook with Wicket, and mostly that was because the examples were out dated. This example is for using Facebook connect with wicket and the facebook-java-api found here: http://code.google.com/p/facebook-java-api/ The docs were not much help, so I feel obligated to put what I did to get things working.
For starters, I added the dependency to maven:
<dependency> <groupId>com.google.code.facebookapi</groupId> <artifactId>facebook-java-api-schema</artifactId> <version>2.1.1</version> </dependency>
We use a Panel to display login information.
<html xmlns:wicket> <body> <wicket:panel> <!-- facebook api as of 8/11/09 --> <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script> <!-- function for facebook to execute on login--> <script type="text/javascript" wicket:id="fbcallback"> function callWicket() { var wcall = wicketAjaxGet('$url$' + '$args$', function() { }, function() { }); } </script> <div id="loginform"> <div wicket:id="fbloginDiv" style="display:none;"> <span wicket:id="fblogin"> <!-- facebool login button --> <fb:login-button onlogin='callWicket();'></fb:login-button> </span> <!-- facebook api --> <script type="text/javascript"> FB.init("your-api-key", "/xd_receiver.htm"); </script> </div> </div> </wicket:panel> </body> </html>
The panel caused me a slight issue when setting up the AbstractDefaultAjaxBehavior. I found that I had to add my Panel to my Page before I could get the callbackUrl from the behavior. Once I did that, everything was ok. Here is a simple panel example that illustrates one way to add the <fb:login to your page and handle a callback from Facebook.
import java.util.ArrayList; import java.util.HashSet; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.wicket.Page; import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.SimpleAttributeModifier; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.protocol.http.WebResponse; import org.apache.wicket.protocol.http.servlet.ServletWebRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.context.SecurityContext; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.context.SecurityContextImpl; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import com.google.code.facebookapi.FacebookException; import com.google.code.facebookapi.FacebookJsonRestClient; import com.google.code.facebookapi.FacebookWebappHelper; import com.google.code.facebookapi.ProfileField; import com.pigspigot.model.User; /** * An example Panel that shows one way to output a facebook login button using fbml * and getting a callback from Facebook. * * @author russellsimpkins@hotmail.com * */ public class SimplePanel extends Panel { private static final Log log = LogFactory.getLog(SimplePanel.class); private WebMarkupContainer fbloginDiv; private Label fblogin; public SimplePanel(String id) { super(id); // TODO Auto-generated constructor stub } /** * This method will the panel */ public void createPanel() { fbloginDiv = new WebMarkupContainer("fbloginDiv"); fbloginDiv.setOutputMarkupId(true).setMarkupId("fbloginDiv"); fblogin = new Label("fblogin", "<fb:login-button onlogin='callWicket();'></fb:login-button>"); fblogin.setEscapeModelStrings(false); fblogin.setOutputMarkupId(true); if (isAuthenticated()) { fbloginDiv.add(new SimpleAttributeModifier("style", "display:none;")); } fbloginDiv.add(fblogin); addOrReplace(fbloginDiv); /** * This will only be called after they're logged in via facebook */ final AbstractDefaultAjaxBehavior behave = new AbstractDefaultAjaxBehavior() { protected void respond(final AjaxRequestTarget target) { // deal with facebook handleFacebookCallback(target.getPage()); fbloginDiv.add(new SimpleAttributeModifier("style", "display:none;")); target.addComponent(fbloginDiv); } }; add(behave); CharSequence url = behave.getCallbackUrl(); StringBuffer sb = new StringBuffer(); sb.append("function callWicket() { \n"); sb.append(" var wcall = wicketAjaxGet('"); sb.append(url); sb.append("', function() { }, function() { });"); sb.append(" }"); Label fbcallback = new Label("fbcallback", sb.toString()); fbcallback.setOutputMarkupId(true); fbcallback.setEscapeModelStrings(false); add(fbcallback); } /** * All that we do to log you in from facebook. I put my fbook.key and fbook.secret in the * properties file. * @param thePage */ public void handleFacebookCallback(Page thePage) { HttpServletRequest req = ((ServletWebRequest) thePage.getRequest()).getHttpServletRequest(); HttpServletResponse res = ((WebResponse) thePage.getResponse()).getHttpServletResponse(); String api = getLocalizer().getString("fbook.key", this); String secret = getLocalizer().getString("fbook.secret", this); FacebookWebappHelper<Object> helper = FacebookWebappHelper.newInstanceJson(req, res, api, secret); // make sure the login worked if (helper.isLogin()) { FacebookJsonRestClient facebookClient = (FacebookJsonRestClient) helper.getFacebookRestClient(); long id; try { // grab the logged in user's id id = facebookClient.users_getLoggedInUser(); // you can bundle ajax calls... facebookClient.beginBatch(); // i'm going to call the users.getInfo fb api call, just to make sure it works ArrayList<Long> ids = new ArrayList<Long>(); ids.add(new Long(id)); // put together a set of fields for fb to return HashSet<ProfileField> fields = new HashSet<ProfileField>(); fields.add(ProfileField.FIRST_NAME); fields.add(ProfileField.LAST_NAME); // get the user data facebookClient.users_getInfo(ids, fields); // execute the batch (which also terminates batch mode until beginBatch is called again) List<? extends Object> batchResponse = facebookClient.executeBatch(false); JSONArray userInfo = (JSONArray) batchResponse.get(0); JSONObject user = userInfo.getJSONObject(0); // a pojo user object User theUser = new User(); String username = user.getString("first_name"); theUser.setUsername(username); // fb emails are proxy, my app needs some kind of holder theUser.setEmail("noreply@facebook.com"); theUser.setUserId(new Integer(0)); theUser.setFacebook(true); theUser.setFacebookId(id); // we use spring, so here we give basic access to the facebook user. List<GrantedAuthority> gaList = new ArrayList<GrantedAuthority>(); gaList.add(new GrantedAuthorityImpl("STANDARD")); theUser.setAuthorities(gaList.toArray(new GrantedAuthority[] {})); GrantedAuthority[] ga = theUser.getAuthorities(); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(theUser, theUser, ga); SecurityContext context = new SecurityContextImpl(); context.setAuthentication(authentication); SecurityContextHolder.setContext(context); } catch (FacebookException e) { log.error("facebook issues: " + e); } catch (JSONException e) { log.error("facebook json issues: " + e); } } } /** * Do your own kind of auth check * @return */ public boolean isAuthenticated() { return SecurityContextHolder.getContext().getAuthentication() != null; } }
The Panel is added to your Page like so:
SimplePanel myPanel = new SimplePanel("your-wicket-id"); // make sure you add the panel first add(myPanel); // now you can create the panel contents myPanel.createPanel();
1 Comment
Don Ferguson
Great writeup. Thanks!
BTW, there is a good solution to the problem:
This has the effect of delaying the call to getCallbackUrl() until render time (when the wicket component hierarchy is all set up). With this change, you can simplify the code, and call createPanel() in the constructor rather than having it called later by the parent.