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:

pom.xml
<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.

SimplePanel.html
<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.

SimplePanel.java

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:

SimplePage.java

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();

  • No labels

1 Comment

  1. Great writeup. Thanks!

    BTW, there is a good solution to the problem:

    "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."

    Use a model for the fbcallback label, as in:
    
           Label fbcallback = new Label("fbcallback", new AbstractReadOnlyModel<String>() {
                @Override
                public String getObject() {
                    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("    }");
                    return sb.toString();
                }
            });
    

    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.