Thursday, March 29, 2012

Workaround for Compilation Errors when Implementing Authenticator in Seam Security and PicketLink IDM

Due to SEAMSECURITY-66 bug, compiling Authenticator implementation will result in compilation errors, as of Seam Security 3.1.0.Final and PicketLink IDM 1.5.0.Alpha2:

[INFO] Compiling 22 source files to /home/ceefour/git/aksimata-web/target/classes [INFO] ------------------------------------------------------------- [ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] LdapAuthenticator.java:[10,30] cannot find symbol symbol : class BaseAuthenticator location: package org.jboss.seam.security [ERROR] LdapAuthenticator.java:[12,34] package org.picketlink.idm.impl.api does not exist [ERROR] LdapAuthenticator.java:[13,40] package org.picketlink.idm.impl.api.model does not exist [ERROR] LdapAuthenticator.java:[23,39] cannot find symbol symbol: class BaseAuthenticator public class LdapAuthenticator extends BaseAuthenticator { [ERROR] LdapAuthenticator.java:[35,22] cannot find symbol symbol : class PasswordCredential location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[46,4] cannot find symbol symbol : class SimpleUser location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[46,26] cannot find symbol symbol : class SimpleUser location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[48,14] cannot find symbol symbol : variable AuthenticationStatus location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[51,14] cannot find symbol symbol : variable AuthenticationStatus location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[52,4] cannot find symbol symbol : method setUser(<nulltype>) location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[56,13] cannot find symbol symbol : variable AuthenticationStatus location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[57,3] cannot find symbol symbol : method setUser(<nulltype>) location: class org.soluvas.ldap.LdapAuthenticator [ERROR] LdapAuthenticator.java:[30,1] method does not override or implement a method from a supertype

Workaround and the "proper" way to include Seam Security is as follows:

<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>         <dependency>                 <groupId>org.jboss.seam.security</groupId>                 <artifactId>seam-security</artifactId>                 <version>${seam.version}</version>                 <scope>compile</scope>         </dependency>         <dependency>                 <groupId>org.picketlink.idm</groupId>                 <artifactId>picketlink-idm-core</artifactId>                 <version>1.5.0.Alpha02</version>                 <scope>compile</scope>         </dependency> </dependencies> </dependencyManagement>

In addition to the actual dependency:

<dependencies>
       <dependency>
            <groupId>org.jboss.seam.security</groupId>
            <artifactId>seam-security</artifactId>
        </dependency>
</dependencies>


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.

How to Deploy Maven Artifacts to WebDAV Repository

Either the DAV wagon is great or DreamHost's Svn+WebDAV integration is great, or both. POM Configuration is straightforward:

<distributionManagement>
<snapshotRepository>
<id>soluvas.org.snapshots.tmp</id>
<url>dav:http://maven.soluvas.org/snapshots</url>
</snapshotRepository>
</distributionManagement>

It "works" in Eclipse sometimes, but when the artifact has not yet been uploaded (huh?!?!) it fails with:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.5:deploy (default-deploy) on project soluvas-apacheds: Failed to deploy artifacts: Could not find artifact org.soluvas.apacheds:soluvas-apacheds:jar:1.0.0-20120329.222133-1 in soluvas.org.snapshots.tmp (dav:http://maven.soluvas.org/snapshots) -> [Help 1]

In mvn CLI, it simply fails: [ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.5:deploy (default-deploy) on project soluvas-apacheds: Failed to deploy artifacts/metadata: No connector available to access repository soluvas.org.snapshots.tmp (dav:http://maven.soluvas.org/snapshots) of type default using the available factories WagonRepositoryConnectorFactory -> [Help 1]

Anyway, to properly do this you need a wagon config, which works well with DreamHost's subversion+webDAV (haven't tried with plain Webdav repository yet, but should work):
<build>
<extensions>
<extension>
<artifactId>wagon-webdav-jackrabbit</artifactId>
<groupId>org.apache.maven.wagon</groupId>
<version>2.2</version>
</extension>
</extensions>
</build>

then you add your credentials in ~/.m2/settings.xml :

<settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">


<servers>

<server>

<id>soluvas.org.snapshots.tmp</id>

<username>youruser</username>

<password>yourpass</password>

</server>

</servers>

...

</settings>

Now you can deploy your Maven project by:

mvn source:jar javadoc:jar install deploy


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.

How to Enable Gzip HTTP Compression in JBoss AS 7.1.1

JBoss AS 7.1.1 has finally returned the powerful Gzip HTTP Compression support (AS7-2991) that was missing since 7.0.0.

Edit standalone/configuration/standalone.xml and add <system-properties> after the </extensions> :

... <extension module="org.jboss.as.weld"/> </extensions> <system-properties> <property name="org.apache.coyote.http11.Http11Protocol.COMPRESSION" value="on"/> </system-properties>

Proof :

$ curl --head --compressed http://localhost:9080/
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"2432-1331360042000"
Last-Modified: Sat, 10 Mar 2012 06:14:02 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Content-Encoding: gzip
Vary: Accept-Encoding
Date: Fri, 30 Mar 2012 00:40:36 GMT

If you're running your app on OpenShift, it doesn't hurt to enable this switch now. That way, when OpenShift upgrades to JBoss AS 7.1.1 or later, your app will get HTTP Compression automatically. :-)

Alternative: You can also enable it by adding to the server launch params :

-Dorg.apache.coyote.http11.Http11Protocol.COMPRESSION=on

Enables HTTP compression on JBoss 7.1.1


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.

Optimal Java EE Webapp Logging Configuration for JBoss AS7 Deployment

I used to configure logging (via SLF4J) in my Java EE web applications like this:

<properties>
    <slf4j.version>1.6.4</slf4j.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>${slf4j.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
    </dependency>
</dependencies>

Although in practice this works well, it's not efficient because I'm bundling my own SLF4J libraries with my webapp, and direct them to JDK Logging, which is handled properly by JBoss AS 7.1.

Note that there's nothing wrong with the above configuration, because makes the webapp very portable across Application Servers (even servlet containers like Tomcat).

Thanks to Joshua Davis, I got the perfect logging configuration for JBoss AS7:

<properties>        <slf4j.version>1.6.4</slf4j.version></properties>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>commons-logging</groupId>

<artifactId>commons-logging</artifactId>

<version>1.1.1</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

<version>${slf4j.version}</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>jcl-over-slf4j</artifactId>

<version>${slf4j.version}</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-jdk14</artifactId>

<version>${slf4j.version}</version>

<scope>provided</scope>

</dependency>

</dependencies>

</dependencyManagement>

Dependencies:

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>jcl-over-slf4j</artifactId>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-jdk14</artifactId>

</dependency>


Since SLF4J is already defined as a JBoss 'module', then all that we need to do is to reference the module from the WAR file.1 The simplest way is to get Maven to list the dependency in META-INF/MANIFEST.MF:

<plugin>

  <groupId>org.apache.maven.plugins</groupId>

  <artifactId>maven-war-plugin</artifactId>

  <version>2.2</version>

  <configuration>

    <archive>

      <manifestEntries>

        <Dependencies>org.slf4j, org.apache.commons.logging</Dependencies>

      </manifestEntries>

    </archive>

  </configuration>

</plugin>

This will generate a META-INF/MANIFEST.MF file that has this line in it:

Dependencies: org.slf4j, org.apache.commons.logging


This tweak saves space, RAM, and probably increases webapp performance a bit too. :-)


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.

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

Friday, March 16, 2012

How to Create a Facebook App using Java EE 6 and Easily Deploy to RedHat OpenShift Cloud [Tutorial]

UPDATE: Please see the updated Facebook App Tutorial with OAuth Support.

________________________________________________________

How to Develop a Facebook App using:
  • 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-tutorial_post_status_to_facebook
This app's interface and purpose is actually simple :
  • Put your Facebook access token in it. You can use the Facebook Graph Explorer and your own app to generate an access token. You need to allow 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).
A Cloud-Ready Facebook App in Java EE in 5 Minutes*
  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 in src/main/webapp/resources/fbstatus/fbstatus_16p.png
    Also save the 16x16 version as an ICO file at src/main/webapp/favicon.ico
  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/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">
    <h:head>
        <title>FBStatus</title>
    </h:head>
    <h:body>
        <p>Hello</p>
    </h:body>
    </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.
  13. 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>
  14. Create the bean src/main/java/org/soluvas/fbstatus/PostView.java :Most of the things below, like always, is Java verbose cruft. (How I wish Scala would prevail over Java the language.) Anyway, here's the code:
    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.application.FacesMessage.Severity;
    import javax.faces.context.FacesContext;
    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;
        String accessToken;
        String message;
        String lastStatusId;
        String lastStatusUrl;
            public void manualPost() {
            FacesContext faces = FacesContext.getCurrentInstance();
            DefaultFacebookClient client = new DefaultFacebookClient(accessToken);
            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 getAccessToken() {
            return accessToken;
        }
        public void setAccessToken(String accessToken) {
            this.accessToken = accessToken;
        }
        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.
    By setting accessToken and message properties then calling manualPost(), you should be able to perform some unit testing on the above code if you want.
  15. Create JSF page for Post Status form
    This page will display the form and call the postView.manualPost() method above.
    Put 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="/templates/basic.xhtml">
    <ui:define name="pageTitle">Post status to Facebook via access token</ui:define>
    <ui:define name="content">
        <div class="row">
                <div class="span12">
                        <div class="row">
                                <h:form>
                    <div class="span12">
                        <h2>Post Status Manually</h2>
                        <p>Access token: <h:inputText value="#{postView.accessToken}"/></p>
                        <p>Message: <h:inputText value="#{postView.message}"/></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>
                    </div>
                    </h:form>
                   
                </div>
                <!--/row-->
            </div>
            <!--/span-->
        </div>
        <!--/row-->
    </ui:define>
    </ui:composition>
    </html>
    The JSF page above simply presents text boxes to input the Facebook access token and 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.
And that's it!
Steps 1-12 are actually pure boilerplate for most JSF apps. Only steps 13, 14, 15 which is 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.
How to Use the App
This app's interface and purpose is actually simple. Deploy the webapp to JBoss AS or to OpenShift (using git push) and :
  • Put your Facebook access token in it. You can use the Facebook Graph Explorer and your own app to generate an access token. You need to allow 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.
This is an example of the status generated by the app :
Fbstatus_openshift_java-ee-6-tutorial_facebook_status_result
You can check it out here: http://www.facebook.com/ceefour/posts/10150732341781672 . Oh and by the way, you're welcome to add me on Facebook :-)


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 ;-)
 
Copyright 2009 Spring vs Java EE Web Dev. Powered by Blogger Blogger Templates create by Deluxe Templates. WP by Masterplan