Monday, March 19, 2012

How to Develop an OpenShift Cloud App with Facebook OAuth Support [Tutorial]

How to Develop a Facebook App with OAuth 2.0 authorization to publish activities to the social network. Technologies:

  • Java EE 6 standards including CDI (JSR-299 Dependency Injection) and JSF 2.0
  • Twitter's excellent CSS framework Bootstrap
  • Easily test and rapidly iterate your app in your local development computer with JBoss AS 7.1.1
  • Deploy to the cloud in seconds to RedHat OpenShift Express, for free!

What Will You Get?

Fbstatus_openshift_java-ee-6_webapp

This app's interface and purpose is actually simple :

  • Login with Facebook and allow the app. The app requires the publish_stream privilege.
  • Put a message, click Post and the app will update your Facebook status.

You check out the live app at fbstatus-soluvas.rhcloud.com.

 

Preparation

Before you start, you'll need to get some basics ready :

  1. Get the latest JBoss Developer Studio 5.0.0.Beta1.
    Simply the quickest way to get started. This contains Eclipse IDE, JBoss Tools, JBoss AS, JBoss Forge, OpenShift plug-in and several other handy tools at your command.
    If you want, you can install these separately:
    - Eclipse IDE, Java EE Developer edition (3.7.2 or later)
    - JBoss Tools (get 3.3.0.Beta1 or later)
    - JBoss AS 7.1.1 or later
    - JBoss Forge 1.0.Final or later

  2. While downloading the things above, signup for RedHat OpenShift Express.
    Set up your SSH keys so you're good to go. You can try creating a test app yourself, but  we'll create another app for this tutorial. (OpenShift allows multiple applications in one account).

  3. You'll also need to have a Facebook app. Go to Facebook Developers and create an app. Any test app will do. We'll need to generate an access token for your Facebook profile (later on).

 

Prepare the Project: Some Boilerplate Stuff

  1. Create a new jbossas-7 OpenShift application.
    In JBoss Developer Studio / Eclipse IDE, create a new OpenShift application :
    File > New > Other... > choose OpenShift Application.
    Name it anything you want, in my case it's fbstatus.
    Pick the jbossas-7 cartridge.
    Other fancy options are not needed for this purpose.

  2. Cleanup the OpenShift scaffold project.
    Delete everything in src/main/webapp except WEB-INF.
    Also delete src/main/webapp/WEB-INF/faces-config.xml first (JBoss Tools may automatically create it for you).
    We delete this so Forge's faces setup (below) runs well.

  3. Add SLF4J Logging dependency.
    JDK Logging is okay, but SLF4J is much nicer. We'll configure SLF4J to log using JDK Logger, which is handled properly by JBoss AS.
    Using Forge Console, run:

    project add-dependency org.slf4j:slf4j-api:1.6.4
    project add-dependency org.slf4j:slf4j-jdk14:1.6.4:runtime

  4. Setup JSF and CDI.
    Using Forge Console, run:

    faces setup

    When asked for CDI, enter "Y".
    For Faces servlet and mapping, enter "N" because it's not required for Servlet 3.0 and beyond.

  5. Make sure Maven project configuration is up-to-date.
    Right click project > Maven > Update Project

  6. Get Icons
    Some people think is an "extraneous" step, but I think initial appearance is very important as it sets your mood. ;-)
    Get a "normal" size icon (~64px) e.g. from IconFinder, and save it as src/main/webapp/resources/fbstatus/fbstatus_64p.png
    Grab or resize the icon to 16x16 (for navbar, buttons, etc.), save it as src/main/webapp/resources/fbstatus/fbstatus_16p.png
    Also save the 16x16 version as an ICO file at src/main/webapp/favicon.ico

    Find your favorite "Login with Facebook" icon, and save it as src/mian/webapp/resources/fbstatus/facebook-login.png

  7. Grab Twitter's Bootstrap HTML5 Framework
    Like the icons above, this is not really "necessary" for the functionality of the apps, but it seems the mood since initially the app will look "acceptable" instead of "plain HTML".
    Download Bootstrap dist and extract it as src/main/webapp/resources/bootstrap
    Also get the jQuery plugins from Bootstrap sources, put the bootstrap-*.js plugins from js/ folder into src/main/webapp/resources/bootstrap/js
    You also need jQuery library litself and put this file as src/main/webapp/resources/jquery.js

  8. Create a JSF template in src/main/webapp/WEB-INF/templates/basic.xhtml :

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core">
    <f:view>
    <h:head>
        <meta charset="utf-8" />
        <title><ui:insert name="pageTitle">Post status to Facebook</ui:insert> | FBStatus</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta name="description" content="Post status to Facebook." />
        <meta name="author" content="FBStatus" />

        <!-- Le styles -->

        <!-- Workaround for JSF resource URLs and css URLs:
        if URL appended with ?ln=bootstrap, glyph URL is wrong:
        http://localhost:9080/fbstatus/faces/javax.faces.resource/img/glyphicons-half...
        should be:
        http://localhost:9080/fbstatus/faces/javax.faces.resource/bootstrap/img/glyph... -->
        <h:outputStylesheet name="bootstrap/css/bootstrap.css"/>
        <style type="text/css">
    body {
        padding-top: 60px;
        padding-bottom: 40px;
    }

    .sidebar-nav {
        padding: 9px 0;
    }
    </style>
        <h:outputStylesheet name="bootstrap/css/bootstrap-responsive.css"/>

        <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
        <!--[if lt IE 9]>
          <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->

        <!-- Le fav and touch icons -->
        <link rel="shortcut icon" href="#{request.contextPath}/favicon.ico" />

        <!--     <link rel="apple-touch-icon-precomposed" sizes="114x114" href="#{request.contextPath}/resources/bootstrap/ico/apple-touch-icon-114-precomposed.png">
        <link rel="apple-touch-icon-precomposed" sizes="72x72" href="#{request.contextPath}/resources/bootstrap/ico/apple-touch-icon-72-precomposed.png">
        <link rel="apple-touch-icon-precomposed" href="#{request.contextPath}/resources/bootstrap/ico/apple-touch-icon-57-precomposed.png"> -->
    </h:head>

    <h:body>

        <div class="navbar navbar-fixed-top">
            <div class="navbar-inner">
                <div class="container">

                    <a class="btn btn-navbar" data-toggle="collapse"
                        data-target=".nav-collapse"> <span class="icon-bar"></span> <span
                        class="icon-bar"></span> <span class="icon-bar"></span>
                    </a> FBStatus
                    <div class="nav-collapse">
                        <ul class="nav">

                            <li>Home</li>
                            <li>Post Status</li>
                            <li>About</li>
                            <li>Contact</li>
                        </ul>
                        <p class="navbar-text pull-right">
                            Login
                        </p>
                    </div>
                    <!--/.nav-collapse -->

                </div>
            </div>
        </div>

        <div class="container">

                    <div class="row">
                <div class="span12">
                    <h:messages/>
                </div>
            </div>

                <ui:insert name="content"/>

            <hr />

            <footer>
            <p>&copy; Hendy Irawan 2012</p>
            </footer>

        </div>
        <!--/.container-->

        <!-- Le javascript
        ================================================== -->

        <!-- Placed at the end of the document so the pages load faster -->
        <h:outputScript name="jquery.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-transition.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-alert.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-modal.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-dropdown.js"/>

            <h:outputScript library="bootstrap" name="js/bootstrap-scrollspy.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-tab.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-tooltip.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-popover.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-button.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-collapse.js"/>

        <h:outputScript library="bootstrap" name="js/bootstrap-carousel.js"/>
        <h:outputScript library="bootstrap" name="js/bootstrap-typeahead.js"/>

    </h:body>
    </f:view>
    </html>

  9. Create a JSF page named src/main/webapp/front.xhtml :

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html">

    <ui:composition template="/WEB-INF/templates/basic.xhtml">

    <ui:define name="pageTitle">Post status to Facebook</ui:define>

    <ui:define name="content">

        <p>Hello</p>

    </ui:define>

    </ui:composition>

    </html>

    Launch the web app using JBoss AS server, you should be able to access the front page using http://localhost:8080/fbstatus/faces/front.xhtml

    You can also deploy to OpenShift by Git committing the entire project and Git push it to OpenShift. It should be accessible at http://yourapp-yourdomain.rhcloud.com/faces/front.xhtml

  10. Add PrettyFaces
    For more SEO-friendly URLs PrettyFaces is a must. Use Forge Console to run:

    project add-dependency com.ocpsoft:prettyfaces-jsf2:3.3.3

    Note: For automatic PrettyFaces configuration, Servlet version in web.xml must be at least 3.0.

  11. Enable PrettyFaces development mode
    This can be turned off in production setting but for now, addd in web.xml as follows :

    <context-param>
    <param-name>com.ocpsoft.pretty.DEVELOPMENT</param-name>
    <param-value>true</param-value>
    </context-param>

    This will allow PrettyFaces mapping to be changed anytime without reloading the webapp.

  12. Create PrettyFaces mapping
    Create src/main/webapp/WEB-INF/pretty-config.xml :

    <pretty-config xmlns="http://ocpsoft.com/prettyfaces/3.3.3"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://ocpsoft.com/prettyfaces/3.3.3
                            http://ocpsoft.com/xml/ns/prettyfaces/ocpsoft-pretty-faces-3.3.3.xsd">

        <url-mapping id="home">
            <pattern value="/" />
            <view-id value="/faces/front.xhtml" />
        </url-mapping>

    </pretty-config>

    The above maps the context root path to /faces/front.xhtml.
    Deploy the webapp in JBoss AS or OpenShift and you should be able to access the front page at webapp root, e.g. in JBoss would be: http://localhost:8080/fbstatus/

Note that these steps that we've done above are all simply boilerplate.
You can save the project at this point and put it somewhere or on Git to get started with a new OpenShift-JSF-Bootstrap project rapidly.

 

A Cloud-Ready Facebook App in Java EE in 5 Minutes*

  1. Add RestFB
    Now let's get to business, I will use RestFB Facebook API library for Java. Using JBoss Forge Console :

    project add-dependency com.restfb:restfb:1.6.9

    There are other alternative libraries for accessing Facebook:
    - Spring Social. It's easy to use and works well. However it drags quite some dependencies (especially the whole Spring Framework), which makes sense, but RestFB serves the purpose of this tutorial quite well.
    - Seam Social. A CDI oriented Facebook client library.
    - facebook-java-api. Actually I tried it before RestFB, but it throws an error regarding invalid session key. I'm using access tokens not session IDs so either it's using an outdated Facebook API version or I didn't read the manual correctly.

    At this point I should have added Seam Faces and Seam International too, to make working with JSF and FacesMessages more convenient. Too bad, Seam artifacts aren't available yet in OpenShift Maven repository. So, plain JSF API for now.

    But in case you're curious how to add Seam Faces and Seam International, here's the needed Maven POM dependencies :

      <properties>
        <seam.version>3.1.0.Final</seam.version>
      </properties>

       <dependencyManagement>
          <dependencies>
             <dependency>
                <groupId>org.jboss.seam</groupId>
                <artifactId>seam-bom</artifactId>
                <version>${seam.version}</version>
                <type>pom</type>
                <scope>import</scope>
             </dependency>
          </dependencies>
       </dependencyManagement>
    ...
            <dependency>
                <groupId>org.jboss.seam.international</groupId>
                <artifactId>seam-international</artifactId>
            </dependency>
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>1.6.2</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.seam.faces</groupId>
                <artifactId>seam-faces</artifactId>
            </dependency>

    I'd love to use Seam Security as well, but due to the similar reason as above, well... let's stick to the raw basics. :-D

  2. Add Apache Commons HttpClient
    project add-dependency org.apache.httpcomponents:httpclient:4.1.3

    We need it because we'll be creating HTTP requests soon.

    Tip: You may also want to add Apache Commons IO because it's very useful in many situations: (but it's not required in this tutorial)

    project add-dependency commons-io:commons-io:2.1

  3. Create FacebookAuthenticator application-scoped bean
    This class forms the bulk of the Facebook authentication logic, and here I implement the entire logic using HTTP operations. For some reason, RestFB does not provide a utility to get an access token (or probably I didn't read the docs well). You can also use an OAuth library. However, this shows actually how simple OAuth 2.0 is in practice.

    Put in src/main/java/org/soluvas/fbstatus/FacebookAuthenticator.java

    package org.soluvas.fbstatus;

    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URI;
    import java.util.List;
    import java.util.Properties;

    import javax.annotation.PostConstruct;
    import javax.enterprise.context.ApplicationScoped;
    import javax.faces.context.ExternalContext;
    import javax.faces.context.FacesContext;
    import javax.inject.Named;
    import javax.ws.rs.core.UriBuilder;

    import org.apache.commons.io.IOUtils;
    import org.apache.http.HttpResponse;
    import org.apache.http.NameValuePair;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.client.utils.URLEncodedUtils;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    /**
     * @author ceefour
     * Handles Facebook OAuth authentication.
     */
    @Named @ApplicationScoped
    public class FacebookAuthenticator {

        private transient Logger log = LoggerFactory.getLogger(FacebookAuthenticator.class);
        private String appId = "";
        private String appSecret = "";
        private URI redirectUri;

            @PostConstruct public void init() throws IOException {
            // Load Facebook App credentials from ${OPENSHIFT_DATA_DIR}/fbstatus.properties if exists,
            // otherwise load from WEB-INF/fbstatus.properties
            String dataDir = System.getenv("OPENSHIFT_DATA_DIR");
            Properties props = new Properties();
            if (dataDir != null) {
                String propsFile = dataDir + "/fbstatus.properties";
                log.info("Loading credentials from file: {}", propsFile);
                FileReader fileReader = new FileReader(propsFile);
                props.load(fileReader);
            } else {
                log.info("Loading credentials from resource: {}", "/WEB-INF/fbstatus.properties");
                InputStream stream = FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream("/WEB-INF/fbstatus.properties");
                if (stream == null)
                    throw new FileNotFoundException("Either ${OPENSHIFT_DATA_DIR}/fbstatus.properties or /WEB-INF/fbstatus.properties must exist.");
                props.load(stream);
            }
            appId = props.getProperty("facebook.app.id");
            appSecret = props.getProperty("facebook.app.secret");
            log.info("App ID: {}", appId);

                    // Generate redirect URI
            final ExternalContext external = FacesContext.getCurrentInstance()
                    .getExternalContext();
            redirectUri = UriBuilder.fromPath(external.encodeActionURL("/faces/fb_auth.xhtml"))
                    .scheme(external.getRequestScheme())
                    .host(external.getRequestServerName())
                    .port(external.getRequestServerPort()).build();
        }

            public URI getRedirectUri() {
            return redirectUri;
        }

        public URI getAuthorizeUri() {
            return UriBuilder.fromUri("https://graph.facebook.com/oauth/authorize").queryParam("client_id", appId)
                .queryParam("redirect_uri", getRedirectUri())
                .queryParam("scope", "publish_stream").build();
        }

            public URI getAccessTokenUri(String authCode) {
            return UriBuilder
                    .fromUri("https://graph.facebook.com/oauth/access_token")
                    .queryParam("client_id", appId)
                    .queryParam("redirect_uri", getRedirectUri())
                    .queryParam("client_secret", appSecret)
                    .queryParam("code", authCode).build();
        }

        /**
         * Sets the authCode received from Facebook and exchanges it with the access token.
         * @param authCode
         * @throws IOException
         * @throws ClientProtocolException
         */
        public String fetchAccessToken(String authCode) throws ClientProtocolException, IOException {
            log.info("Retrieving access token using authCode {}", authCode);
            URI accessTokenUri = getAccessTokenUri(authCode);
            DefaultHttpClient client = new DefaultHttpClient();
            HttpGet accessTokenReq = new HttpGet(accessTokenUri);
            HttpResponse response = client.execute(accessTokenReq);
            if (response.getStatusLine().getStatusCode() != 200)
                throw new IOException(String.format("GET %s throws HTTP Error %d: %s",
                        accessTokenUri, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
            Scanner scanner = new Scanner(response.getEntity().getContent());
            ArrayList<NameValuePair> data = new ArrayList<NameValuePair>();
            URLEncodedUtils.parse(data, scanner, "UTF-8");
            // Probably due to non-existing Content-Encoding, this one is not working:
    //        List<NameValuePair> data = URLEncodedUtils.parse(response.getEntity());
            // see: https://issues.apache.org/jira/browse/HTTPCLIENT-1175
            String accessToken = data.get(0).getValue();
            //log.debug("Access token = {}", fbAccessToken); // security risk?
            return accessToken;
        }

        }

  4. Create the UserSession session-bean
    This bean handles the OAuth code that provided by Facebook, asks the FacebookAuthenticator to exchange it with an access token, then stores the access token in the session.

    Put in src/main/java/org/soluvas/fbstatus/UserSession.java :

    package org.soluvas.fbstatus;

    import java.io.IOException;
    import java.io.Serializable;

    import javax.enterprise.context.SessionScoped;
    import javax.faces.context.ExternalContext;
    import javax.faces.context.FacesContext;
    import javax.inject.Inject;
    import javax.inject.Named;

    import org.apache.http.client.ClientProtocolException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    @Named @SessionScoped
    public class UserSession implements Serializable {

        private static final long serialVersionUID = 1L;
        private transient Logger log = LoggerFactory.getLogger(UserSession.class);
        @Inject FacebookAuthenticator facebookAuth;
        private String fbAccessToken;

            public String getFacebookAuthCode() {
            return null;
        }

            /**
         * Sets the authCode received from Facebook and exchanges it with the access token.
         * @param authCode
         * @throws IOException
         * @throws ClientProtocolException
         */
        public void setFacebookAuthCode(String authCode) throws ClientProtocolException, IOException {
            String accessToken = facebookAuth.fetchAccessToken(authCode);
            setFbAccessToken(accessToken);

                    log.info("Access token received, redirecting to Post Status page");
            ExternalContext external = FacesContext.getCurrentInstance().getExternalContext();
            external.redirect(external.encodeActionURL("/faces/post.xhtml"));
        }

        public String getFbAccessToken() {
            return fbAccessToken;
        }

        public void setFbAccessToken(String fbAccessToken) {
            this.fbAccessToken = fbAccessToken;
        }

        }

  5. Create the "login button"
    The login "button" is actually a normal link to a Facebook URL with special parameters.

    Edit the basic.xhtml template as follows:

    <p class="navbar-text pull-right">
       
    </p>

    Also edit the contents of the ui:define name="content" in front.xhtml page as follows:

    <div class="row">

        <div class="span12">
            <div class="hero-unit">
                <h:graphicImage library="fbstatus" name="fbstatus_64p.png"
                    style="float: right;" alt="FBStatus" />
                <h1>FBStatus</h1>
                <p>Post status to Facebook...</p>
                <p>Easy!</p>
                <p>
                    Login with Facebook »
                </p>
            </div>

        </div><!--/span-->

        </div><!--/row-->

    The login button/link uses the URI given by "FacebookAuthenticator.authorizeUri" bean, which is implemented like this:

    public URI getAuthorizeUri() {
        return UriBuilder.fromUri("https://graph.facebook.com/oauth/authorize").queryParam("client_id", appId)
            .queryParam("redirect_uri", getRedirectUri())
            .queryParam("scope", "publish_stream").build();
    }

    As you can see, the Facebook authorization URL has 3 query parameters:
    - client_id: Your Facebook App ID
    - redirect_uri: the destination URL after authorization complete
    - scope: Requested permissions, hardcoded to "publish_stream" in this case. You can see the complete list of permissions in Facebook Graph API documentation.

  6. Create the Facebook Authentication Receiver
    We need a page that handles the OAuth verifier code given by Facebook.
    Put in src/main/webapp/fb_auth.xhtml :

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html">

    <f:metadata>
        <f:viewParam name="code" value="#{userSession.facebookAuthCode}"/>
    </f:metadata>

    <h:head>
        <title>Facebook Authentication</title>
    </h:head>
    <h:body>
    </h:body>

    </html>

    Also add the mapping in pretty-config.xml :

    <url-mapping id="fb_auth">
        <pattern value="/fb_auth" />
        <view-id value="/faces/fb_auth.xhtml" />
    </url-mapping>

    The page code is very simple because it simply calls UserSession.setFacebookAuthCode(), which in turn delegates the real work to FacebookAuthenticator bean.

  7. Create the Post Status form
    UserSession redirects to /faces/post.xhtml, however we haven't created this page.
    Put this in /src/main/webapp/post.xhtml :

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html">

    <ui:composition template="/WEB-INF/templates/basic.xhtml">

    <ui:define name="pageTitle">Post status to Facebook</ui:define>

    <ui:define name="content">

        <div class="row">
            <div class="span12">

                        <h:form>
                    <h2>Post Status</h2>
                    <p><h:inputTextarea value="#{postView.message}" style="width: 30em;" rows="4"/></p>
                    <p><h:commandLink action="#{postView.manualPost}" value="Post" styleClass="btn"/></p>

                    <h:panelGroup rendered="#{not empty postView.lastStatusId}">
                        <p>Last status ID: #{postView.lastStatusId}</p>
                    </h:panelGroup>

                                    <p><small>Access token: #{userSession.fbAccessToken}</small></p>
                </h:form>

            </div> <!--/span-->
        </div> <!--/row-->

    </ui:define>

    </ui:composition>

    </html>

    The JSF page above simply presents text boxes to input the desired message, and a button to call the PostView.manualPost() method.

    Also displayed is the link to the last status so you can see it in Facebook website.

  8. Create the bean which posts status to Facebook using RestFB
    This is the backing bean that powers the post.xhtml form above.

    Put in src/main/java/org/soluvas/fbstatus/PostView.java :

    package org.soluvas.fbstatus;

    import java.io.Serializable;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    import javax.enterprise.context.SessionScoped;
    import javax.faces.application.FacesMessage;
    import javax.faces.context.FacesContext;
    import javax.inject.Inject;
    import javax.inject.Named;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import com.restfb.DefaultFacebookClient;
    import com.restfb.Parameter;
    import com.restfb.types.FacebookType;

    @SessionScoped @Named
    public class PostView implements Serializable {

        static final long serialVersionUID = 1L;
        private transient Logger log = LoggerFactory.getLogger(PostView.class);
    //    @Inject Messages messages;
        @Inject UserSession userSession;
        String message;
        String lastStatusId;
        String lastStatusUrl;

            public void manualPost() {
            FacesContext faces = FacesContext.getCurrentInstance();
            DefaultFacebookClient client = new DefaultFacebookClient(userSession.getFbAccessToken());
            FacebookType response = client.publish("me/feed", FacebookType.class, Parameter.with("message", message));
            lastStatusId = response.getId();
            log.info("Got Post ID: {}", lastStatusId);
            Matcher matcher = Pattern.compile("(\\d+)_(\\d+)").matcher(lastStatusId);
            if (matcher.matches()) {
                String userId = matcher.group(1);
                String postId = matcher.group(2);
                lastStatusUrl = "http://www.facebook.com/" + userId + "/posts/" + postId;
                log.info("Post URL is {}", lastStatusUrl);
                faces.addMessage(null, new FacesMessage(
                        "Status posted to Facebook with ID: " + lastStatusId, ""));
    //            messages.info("Status posted to Facebook with ID: {0}", lastStatusId);
                message = "";
            } else {
                log.error("Cannot parse Post ID: {}", lastStatusId);
                faces.addMessage(null, new FacesMessage(faces.getMaximumSeverity(),
                        "Cannot parse Post ID: " + lastStatusId, ""));
            }
        }

            public String getMessage() {
            return message;
        }
        public void setMessage(String message) {
            this.message = message;
        }

        public String getLastStatusUrl() {
            return lastStatusUrl;
        }

        public void setLastStatusUrl(String lastStatusUrl) {
            this.lastStatusUrl = lastStatusUrl;
        }

        public String getLastStatusId() {
            return lastStatusId;
        }

        public void setLastStatusId(String lastStatusId) {
            this.lastStatusId = lastStatusId;
        }

        }

    This is the core meat of this tutorial. As you can see the real code is not very complicated. :-) In Java EE 6, you use managed beans and annotate it with @Named to make it accessible from JSF page (below). By annotating with @SessionScoped the bean's property values are preserved within the same browser session. Different visitors or different browsers will have its own instance of the session-scoped bean.

    If you want to perform unit testing, you need to supply the (probably mock) UserSession object, set message property then call manualPost().

And that's it!

As you can see only a few steps are unique to this web application. Which highlights the necessity to create some form of project template or prototype where you can easily construct the basic JSF webapp necessities quickly.

Deploying to OpenShift

Deploying the app is easy, simply do a "git push".

The app requires a one-time setup. The Facebook App credentials are not stored in the application, but must be configured separately (typically by the sysadmin aka DevOps guy).

You need to create a file in your OpenShift data directory (environment value OPENSHIFT_DATA_DIR) named fbstatus.properties .

There are several ways to create this file, but here I'll show you how to do it with SSH.

Use the rhc app show command to find out the Git / SSH URL:

$ rhc app show -a fbstatus

Application Info
================
fbstatus
    Framework: jbossas-7
     Creation: 2012-03-15T00:22:35-04:00
         UUID: 7652c51ac0b84512b5edef59f00***
      Git URL: ssh://7652c51ac0b84512b5edef59f00***@fbstatus-soluvas.rhcloud.com/~/git/fbstatus.git/
   Public URL: http://fbstatus-soluvas.rhcloud.com/

 Embedded:
      mongodb-2.0 - Connection URL: mongodb://127.13.155.1:27017/

Now you should be able to connect via SSH by using the depicted username and hostname:

ssh 7652c51ac0b84512b5edef59f00***@fbstatus-soluvas.rhcloud.com

Once logged in, go to the fbstatus/data directory and create the fbstatus.properties file using cat :

cd fbstatus/data
cat > fbstatus.properties
facebook.app.id=YOUR_APP_ID
facebook.app.secret=YOUR_APP_SECRET
( press Ctrl+D )

Now it should look like this:

[fbstatus-soluvas.rhcloud.com data]\> ls -l
total 4
-rw-r--r--. 1 86 Mar 19 04:32 fbstatus.properties

Reload your app (using rhc app reload) and the app should work fine.

How to Use the App

This app's interface and purpose is actually simple. After deploying the app:

  • Click Login with Facebook button. The user will be shown a series of Facebook authorization screens before being redirected back to the site:

    Fbstatus_openshift_java-ee-6_allow
    Fbstatus_openshift_java-ee-6_authorize
  • Put a message, click Post and the app will update your Facebook status.

You check out the live app at fbstatus-soluvas.rhcloud.com.

This is an example of the status generated by the app :

Fbstatus_openshift_java-ee-6_status

You can see the status update at http://www.facebook.com/ceefour/posts/10150732341781672 . Oh and by the way, you're welcome to add me on Facebook :-)

To make it even more convenient for you, the entire project for this tutorial is available as open source soluvas/fbstatus project at GitHub. It's directly deployable to OpenShift too!

 

How to Test the App Locally using JBoss AS

Testing the app locally very similar with OpenShift, but you need to use JBoss AS.

To provide the fbstatus.properties configuration file, you can either:

  • Set the OPENSHIFT_DATA_DIR environment yourself. This way mirrors how OpenShift environment works. Or:

  • Put the fbstatus.properties file in src/main/webapp/WEB-INF folder. This is an additional  logic I provided to make it more in-line with "traditional" Java EE apps.

For application settings in Facebook developer site, you need to put "http://localhost/" as your website URL. So that Facebook will allow your app to redirect to "localhost" address (your development machine).

 

To learn more about Java Web Development using Java EE 6, I highly recommend The Java EE 6 Tutorial: Basic Concepts (4th Edition) (Java Series) by Eric Jendrock, Ian Evans, Devika Gollapudi and Kim Haase.

* (to be honest, no, I didn't write this article in 5 minutes. but you could ;-)

13 comments:

Facebook Application Developer said...

Pretty cool and nice information to me, i am really impressed by this site.

coach factore outlet said...

Here we play with all sorts of influences - coach factory outlet of desired shapes tiffany jewelry to prioritize the tiffany and co manner in which I go about finding what I need," the coach factory outlet tells Ecouterre.More than 1,000 runners began the race.Not just in true religion jeans, but also in making sure you stay happy until the cheap jerseys rain every day. Hey, $350 is way less than those coach outlet. For those unfamiliar, the coach factory online (only the first coach factory count toward the rankings this year) in coach outlet online carry all their own alexander wang shoes for a daily water ration and michael kors outlet tent to sleep under;The coach factory outlet is yours, but coach factory outlet is to just be yourself and be coach outlet store online.The sleek ensemble came on show through sac burberry. Choose from brands like Bernardo, michael kors outlet and others.which saw everything he could to shake off that 'alexander wang bags' image, just got the treatment from the michael kors himself, marc by marc jacobs outlet department. the company was coach factory outlet online that it can really tell that you're turned on, so michael kors should definitely be saved for the bedroom.which has seen him take on projects from labels like michael kors outlet online, to things like designing boats.

Anonymous said...

This is such a great resource that you are providing and you give it away for free. I enjoy seeing websites that understand the value of providing a prime resource for free. I truly loved reading your post. gavinmatthews |

fineartsprints |

theww |

euronovosti |

kuldeepbrar |

fazackarley |

voiceq |

pipelinemats |

journalistsforum |

claims-id |

polo outlet said...

http://www.burbagssale2014.com/ Burberry Outlet
http://www.airmaxshoesfactory.com/ Air Max Shoes
http://www.coachblackfriday2014.com/ Coach Black Friday
http://www.coach-storeoutletonline.com/ Coach Black Friday
http://www.coachcoachoutlet.com/ Coach Cyber Monday
http://www.coachxfactory.com/ Coach Factory
http://www.coach-factoryoutletonline.net/ Coach Outlet Factory
http://www.coach-outletonlineusa.com/ Coach Outlet USA
http://www.coach-pursesfactory.com/ Coach Purses Factory
http://www.coachpurseusa.com/ Coach Purses USA
http://www.coach-storeoutlet.com/ Coach Store Outlet
http://www.coach-pursesonline.com/ Coach Purses On Sale
http://www.monsterbeatsbydres.com/ Monster Beats Outlet
http://www.louis-vuittonblackfriday.com/ Louis Vuitton Outlet
http://www.lv-guccishoesfactory.com/ Louis Vuitton Factory
http://www.marcjacobsonsale.com/ Marc Jacobs On Sale
http://www.mcmworldwides.com/ MCM Outlet
http://www.mcmoutlet-jp.com/ MCM 店铺
http://www.oakleysunglassesfactory.com/ cheap oakley sunglasses
http://www.michaelkorsmas.com/ Michael Kors Outlet
http://www.michaelkors.so/ Michael Kors Outlet
http://www.michaelkorsfactory-store.com/ Michael Kors Factory
http://www.michaelkorsoutletr.com/ Michael Kors Outlet
http://www.michael-korsfactoryonline.com/ Michael Kors Factory Online
http://www.newcoachfactoryoutlet.com/ Coach Factory Outlet
http://www.north-faceoutletonlines.net/ North Face Outlet Online
http://www.polo-outletstore.com/ Polo Outlet Store
http://www.ralph-laurenhome.com/ Ralph Lauren UK
http://www.saclongchamppairs.com/ Sac Longchamp Pairs
http://www.tcoachoutletonline.com/ Coach Outlet Online
http://www.the-coachfactoryoutlet.com/ Coach Factory Oultet
http://www.barbour-jacketsoutlet.com/ Barbour Jackets Outlet Online
http://www.canada-gooser.com/ Canada Goose Outlet
http://www.guccishoesuk-2014.com/ Gucci Outlet Online
http://www.michaelkorsstates.com/ Michael Kors Outlet
http://www.moncler-clearance.com/ Moncler Clearance
http://www.moncler-jacketsoutletonline.com/ Moncler Jackets Outlet Online
http://www.northsclearance.com/ North Clearace Outlet
http://www.polo-ralphlaurenonline.com/ Polo Ralph Lauren Outlet Online
http://www.woolrich-clearance.com/ Woolrich Clearance
http://www.cvshopfactory.com/ shop.coachfactory.com
http://www.mksfactoryoutlet.com/ Michael Kors Factory Outlet
http://www.zxcoachoutlet.com/ Coach Outlet Online USA
http://www.thebeatsbydre.net/ Beats by Dre
http://www.superbagsmarket.com/
http://www.newoutletonlinemall.com/ Coach Purses Outlet Online
http://www.clickmichaelkors.com/ Michael Kors USA

Neha Kapoor said...

happy to find this information searching since long time

Cloud Hosting Services

oakleyses said...

burberry handbags, prada outlet, ray ban sunglasses, gucci handbags, tiffany and co, louis vuitton, christian louboutin outlet, christian louboutin uk, tory burch outlet, ray ban sunglasses, polo ralph lauren outlet online, louis vuitton outlet, jordan shoes, michael kors outlet online, nike air max, christian louboutin, ray ban sunglasses, louis vuitton outlet, longchamp outlet, prada handbags, michael kors outlet online, uggs outlet, christian louboutin shoes, uggs on sale, ugg boots, louis vuitton outlet, chanel handbags, longchamp outlet, oakley sunglasses, nike outlet, louis vuitton, uggs outlet, polo outlet, michael kors outlet, burberry outlet, tiffany jewelry, michael kors outlet online, oakley sunglasses, nike air max, ugg boots, michael kors outlet online, oakley sunglasses, replica watches, nike free, michael kors outlet, replica watches, longchamp outlet, kate spade outlet

oakleyses said...

sac vanessa bruno, hollister uk, polo lacoste, vans pas cher, nike roshe run uk, nike free uk, true religion outlet, air max, polo ralph lauren, coach purses, louboutin pas cher, hollister pas cher, kate spade, lululemon canada, timberland pas cher, michael kors outlet, sac longchamp pas cher, michael kors pas cher, ray ban pas cher, true religion outlet, abercrombie and fitch uk, nike air max uk, replica handbags, nike air max uk, nike blazer pas cher, coach outlet, nike roshe, michael kors, mulberry uk, coach outlet store online, burberry pas cher, guess pas cher, nike air max, sac hermes, michael kors, north face uk, hogan outlet, north face, converse pas cher, oakley pas cher, ralph lauren uk, true religion outlet, new balance, jordan pas cher, nike free run, nike tn, longchamp pas cher, true religion jeans, nike air force, ray ban uk

oakleyses said...

hollister, north face outlet, mac cosmetics, baseball bats, nike huaraches, babyliss, new balance shoes, iphone cases, chi flat iron, louboutin, ferragamo shoes, iphone 5s cases, longchamp uk, soccer shoes, mcm handbags, nike roshe run, timberland boots, mont blanc pens, vans outlet, bottega veneta, instyler, oakley, ralph lauren, valentino shoes, giuseppe zanotti outlet, s6 case, p90x workout, celine handbags, iphone 6s plus cases, herve leger, north face outlet, nike trainers uk, ipad cases, iphone 6s cases, lululemon, wedding dresses, nike air max, abercrombie and fitch, nfl jerseys, hermes belt, insanity workout, ghd hair, asics running shoes, beats by dre, iphone 6 cases, reebok outlet, jimmy choo outlet, soccer jerseys, hollister clothing, iphone 6 plus cases

oakleyses said...

pandora jewelry, canada goose uk, canada goose, barbour, moncler, karen millen uk, swarovski crystal, replica watches, louis vuitton, canada goose, louis vuitton, juicy couture outlet, nike air max, lancel, swarovski, canada goose jackets, gucci, converse, thomas sabo, canada goose outlet, pandora charms, ugg,uggs,uggs canada, juicy couture outlet, doudoune moncler, hollister, moncler, montre pas cher, coach outlet, links of london, hollister, ugg uk, ugg, toms shoes, marc jacobs, pandora uk, vans, canada goose, moncler, canada goose outlet, moncler outlet, moncler outlet, louis vuitton, moncler, ray ban, louis vuitton, louis vuitton, converse outlet, moncler uk, ugg,ugg australia,ugg italia, supra shoes, barbour uk

KathleenJordan said...

I really enjoyed this posting in which you share a valuable post. Thanks for sharing it.
Facebook app development companies

Getit said...

Nice post it is. Thanks for this information
Zapya : Download Zapya for PC
Zapya
Zapya free download
Zapya download
Zapya apk
Zapya for Mac
Zapya for ios Zapya app to share files and folders

Nithyanantham M said...

Thanks for sharing this post
Dailymotion video downloader

Try Dailymotion video
downloader
to download videos from dailymotion.

Nithyanantham M said...

Thanks for sharing this post
Dailymotion video downloader

Try Dailymotion video
downloader
to download videos from dailymotion.

Post a Comment

 
Copyright 2009 Spring vs Java EE Web Dev. Powered by Blogger Blogger Templates create by Deluxe Templates. WP by Masterplan