Tutorial

Tutorial: JSF 2 and HTML5 Server Sent Events

RogerKitain
jsf-2-0

JSF co-spec lead Roger Kitain introduces us to composite components, combining JSF 2.0 with HTML5 Server Sent Events to form a powerful mix

The world of HTML5 offers many
features to facilitate the creation of exciting and dynamic web
sites. There are web communication specifications under the HTML5
“umbrella” that define a standard way to perform real-time
communication between browser clients and servers. One such
specification is the Server Sent Events specification. In
this article, we will explore how we can incorporate HTML5 Server
Sent Events into a JavaServer Faces (JSF) User Interface using some
of the new component features available since JSF 2.0.

HTML5 Server
Sent Events (SSE) is a W3C specification that defines a standard
API for opening an HTTP connection for receiving push notifications
(data) from the server.
Operationally, it works the same as
Comet. However, Comet attempts to facilitate
“server push” over the existing HTTP request/response protocol
through various hacks such as appending scripts to iframes. The SSE
specification introduces a new
EventSource HTML element
that can be used with JavaScript to establish a connection to a
server end point.
The client also registers event callback
JavaScript functions for handing “pushed” data from the server.
Let’s look at a client example:

Listing 1:
Client Page

 

 <!DOCTYPE html>
    <head>
       <script type='text/javascript'>
          var source = new EventSource('serverevents');
          source.onmessage = function(event) {
             var ev = document.getElementById('events');
             ev.innerHTML += event.data; 
          };
       </script>
   </head>
   <body>
      <div id=”events” />
   </body>

 

This example shows a simple page
that will establish a connection to a server endpoint when the page
is loaded. On line 4, we create a new EventSource object, feeding
it the URL of a server endpoint. The server
endpoint could be a PHP script, NodeJS implementation, or even a
Java Servet. When a new EventSource instance is created, an initial
GET request to the server endpoint is done to establish the initial
connection.
On line 5, we register a message callback that
will populate the events div with the event data. We could
have also registered a message handler on the EventSource instance
this way:

 

source.addEventListener('message', 'msgCallback', false);

 

As data is
pushed from the server, this callback will update the div area in
the page.
It is also possible to handle multiple event types
from the server:

 

source.addEventListener('ticker', 'tickerCallback', false);
source.addEventListener('time', 'clockCallback', false);

 

The event data for these events would
be pushed over the same EventSource connection. It should be
mentioned that SSE (namely the EventSource API) is not yet
available in all browsers. Google Chrome is one such browser that
has SSE support.

Server Requirements

The SSE
specification specifies requirements for the server response. The
specification states that the response content type must be

text/event-stream. To send a single
line of data (Hello World!):

 

data: Hello World!nn

 

To send multiple lines of data:

 

data: This is the first linen
data: This is the second linenn

 

Here, a single string of data
concatenated with the newline character will be sent to the client
side event handler function. It is also possible to send the
response as JSON data:

 

data: {n
data: "msg":"hello world",n
data: "id:"12345n
data: }nn

 

In all cases, notice that two
successive newline characters end the response.

The User Interface

We’ll look at a simple “Stock View”
application that will cause the streaming of three event types to
the JSF user interface. This user interface
consists of three areas of information. The first area is the
“time” bar. The second area is the stock quote area that consists
of an input field to enter stock quotes, buttons to retrieve stock
quote information and reset (clear) the input area, and a table
that displays the stock quote information “real-time” as it is
updated on the server. The quote numbers are randomly generated on
the server, but real quotes could be retrieved by tying into a
quote service. The third area consists of a link to retrieve RSS
news feeds for the stocks, and a scrollable area that displays the
news feed. The news feed is also pushed from the server
“real-time”. Corresponding to these three areas are three SSE event
types:
time, ticker and
rss. When the user interface is first displayed, the clock will
tick real time. You enter space delimited stock symbols, press
the
Get Quotes button and a
table is dynamically displayed with streaming quotes. Pressing
the
Get Stock News
link causes news feeds to stream from the
server.

The User Interface Markup

JSF 2.0 introduced three big
features that make it easier to develop dynamic user
interfaces.

  • Facelets becomes the primary view technology
  • Composite components make it easier to develop complex
    components
  • Ajax support

Let’s see how
these three features are used in this user interface.

Listing 2 is the markup for the main user interface.

Listing 2: stock.xhtml
Facelets View

 

 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:h5="http://java.sun.com/jsf/composite/h5"
       xmlns:ez="http://java.sun.com/jsf/composite/stockinfo">
     <h:head>
        <meta charset="UTF-8" />
   </h:head>

   <h:body style="background: #fefeef">

      <h:form>
          <h:panelGrid styleClass="title-panel">
             <h:panelGrid columns="2" cellpadding="20">
                <h:outputText value="S t o c k V i e w" styleClass="title-panel-text"/>
             </h:panelGrid>
             <h:outputText value="Powered By JavaServer Faces 2.1 and Html 5 Server Sent Events "
                styleClass="title-panel-subtext"/>
          </h:panelGrid>

           <h:outputStylesheet name="stylesheet.css" />

           <h5:sse url="/JSFStock1/sse/stockticker" events="{stock:stockHandler,time:clockHandler,rss:rssHandler}"/>

           <br/>
           <ez:clock/>
           <br/>
           <br/>
           <ez:stock/>

           <h:messages/>

       </h:form>

   <a href="doc.xhtml">Documentation</a>
   </h:body>
 </html>

 

This user
interface uses four composite components.
We’ll look at
these in more detail later on, but I summarize them here:

  • sse: Server Sent Events component
    used to establish a connection to a server end point.
  • clock: Clock component displays the
    ticking time bar
  • stock: Stock component that
    produces the input field for entering stock symbols, the two
    buttons, and a placeholder for the dynamic table that will display
    stock ticker information.
  • rss: Component that produces the
    link
    Get Stock News and a
    placeholder for the dynamic scrollable area that will display stock
    news. This component is actually contained in the implementation of
    the stock component which is why you don’t see it in this
    view.

The first thing you’ll notice is
that on line 1 we’re using an HTML5 document type declaration. That
makes this document an HTML5 polyglot document because it has an
HTML5 document type /namespace and well-formed XHTML syntax – which
works great for JSF views. This would allow us to use other HTML5
features as well. Let’s take a look at the other relevant sections
of this view:

Lines 3
– 7
: Here we declare the namespaces used in the user
interface. Lines 3 and 4 are just the standard JSF namespaces. Line
5 is the Facelets namespace. Line 6 declares the namespace that
we’ll use for our SSE component. Line 7 declares the namespace that
will be used for the
clock, stock and rss components.

 Line 25: This is the usage of
the SSE component. This component uses two attributes:

  • url: The URL of the server endpoint that we are
    connecting to
  • events: Specifies event
    type : event handler
    pairings. The
    event handler is the name of the
    JavaScript function that will handle the associated

    event type data coming from the
    server.

Line 28: This is the usage
of the
clock component.

 Line
31
: This is the usage of the
stock
component.

It’s easy for a page author to use
well defined components in a view. Now let’s see how these
components are defined.

 

The Composite Components

Recall that JSF composite
components consist of two areas:

  • interface section
  • implementation section

The
interface section is where you define
the usage contract for the component. It is where you define the
attributes that a page author would use with your component.
The
implementation section is
the code that produces the component. It may consist of HTML
markup, JavaScript and/or other JSF components. Let’s take a look
at the composite components that are used in this
application.

The SSE Composite Component

The Server Sent Events (SSE)
composite component is used to establish a connection to a server
endpoint. As mentioned earlier, this component
has two attributes. Let’s see how this component is
implemented.

Listing 3: sse.xhtml SSE Composite Component

 

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:composite="http://java.sun.com/jsf/composite">
<head>

<title>Server Sent Event Composite Component</title>

</head>

<body>

<composite:interface>
   <composite:attribute name="url" required="true"/>
   <composite:attribute name="events" required="false"/>
</composite:interface>

<composite:implementation>
   <h:outputScript name="js/jsf-sse.js" target="head" />
   <script type="text/javascript">
        JSF.sse.connect("#{cc.attrs.url}", #{cc.attrs.events});
   </script>
</composite:implementation>

</body>

</html>

 

Let’s take a look at the relevant
sections of this component.

Lines 4
– 7
: This is just our namespace declarations. Line 7 is
declaring a
composite namespace
that will be used to define the relevant sections of our composite
component.

Lines 16
– 19:
This is where we define the

interface or usage contract for our
composite component. On line 17 we define a required

url attribute whose value will be the
server endpoint. On line 18 we define the
events
attribute whose value will contain
event type : event handler name mappings as shown earlier.

Lines 21
– 26:
This is the implementation of the composite
component. JavaScript and JSF work together nicely to create
dynamic components. On line 22, we are loading a small JavaScript
library that contains functions for performing SSE operations for
JSF. Notice that we’re using a JSF component

<h:outputScript> to specify the
target location of the JavaScript file. On line 24 we are using a
function from the JSF SSE JavaScript library to establish a
connection to the server endpoint using the
url
and events attribute values. 

So, the idea is that you can drop
one of these components in your JSF view to connect to a server
endpoint. Let’s take a look at the JavaScript library that contains
the functions for performing SSE actions.

Listing 4: jsf-sse.js The JSF SSE JavaScript Library

 

1.  if (!JSF) {
2.  
3.      /**
4.       * The top level global namespace for JavaServer Faces Server Sent Event
5.       * functionality. 
6.       * @name JSF
7.       * @namespace
8.       */
9.      var JSF = {};
10.
11.     /**
12.      * The namespace for Server Sent Event functionality. 
13.      * @name JSF.sse
14.      * @namespace
15.      * @exec
16.      */
17.     JSF.sse = function() {
18.
19.         var eventSource = null;
20. 
21.         var getEventSource = function getEventSource(url) {
22.             url = 'http://' + document.location.host + url;
23.             eventSource = new EventSource(url);
24.         };
25.
26.         return {
27.
28.             /**
29.              * Connect to a server end point. 
30.              * <p><b>Usage:</b></p>
31.              * <pre><code>
32.              * JSF.sse.connect(url, {events: 'stock:stockHandler time:clockHandler'});
33.              * ... 
34.              * function stockHandler(event) {
35.              * ... 
36.              * }
37.              * </pre></code>
38.              *
39.              * @member JSF.sse
40.              * @param url The URL of a valid server end point that will deliver messages. 
41.              * @param eventOptions The set of events consisting of event name:handler name pairs
42.              *   associating an event name from the server, and the name of the handler that
43.              *   will process the event.. 
44.              */
45.             connect: function connect(url, eventOptions) {
46.                 if (url === null) {
47.                     throw new Error("Must specify URL");
48.                 }
49.                 getEventSource(url);
50.                 if (eventSource !== null) {
51.                     if (eventOptions !== null) {
52.                         for (var i in eventOptions) {
53.                            JSF.sse.addOnEvent(i, eventOptions[i]);
54.                         }
55.                     }
56.                }
57.
58.             },
59. 
60.             /**
61.              * Register a callback for error handling. 
62.              * <p><b>Usage:</b></p>
63.              * <pre><code>
64.              * JSF.sse.addOnError(handleError); 
65.              * ... 
66.              * var handleError = function handleError(data) {
67.              * ... 
68.              * }
69.              * </pre></code>
70.              *
71.              * @member JSF.sse
72.              * @param callback a reference to a function to call on an error
73.              */
74.             addOnError: function addOnError(callback) {
75.                 if (eventSource !== null) {
76.                     if (typeof callback === 'function') {
77.                         eventSource.addEventListener('error', callback, false);
78.                     }
79.                 }
80.
81.             },
82.
83.             /**
84.              * Register a callback for event handling. 
85.              * <p><b>Usage:</b></p>
86.              * <pre><code>
87.              * JSF.sse.addOnEvent('timeEvent', handleEvent); 
88.              * ... 
89.              * var handleEvent = function handleEvent(data) {
90.              * ... 
91.              * }
92.              * </pre></code>
93.              *
94.              * @member JSF.sse
95.              * @param eventName the event name associated with the message. 
96.              * @param callback a reference to a function to call for processing messages with a specific event
97.              * name. 
98.              */
99.             addOnEvent: function addOnEvent(eventName, callback) {
100.                if (eventSource !== null) {
101.                     if (typeof callback === 'function' && eventName !== null
102.                           && typeof eventName !== 'undefined') {
103.                           eventSource.addEventListener(eventName, callback, false);
104.                     }
105.                }
106.
107.            }
108.         };
109.
110.     }();
111. } 

 

Line 9: JSF
namespace declaration.

Line
21:
A private function that creates an

EventSource instance to connect to the
server endpoint specified by the provided

url.

Line
45:
The public
connect function that is used in the SSE composite component in
Error: Reference source not found. This function calls through to
the private function
getEventSource and registers the application callback functions using a
public
addOnEvent function (line
99).

Line
74:
A public utility function for registering an error
processing callback function. With SSE you can register a function
with the
error event
type.

Line 99: A public
utility function for registering callback functions for processing
event types.

The Clock Composite Component

The clock composite
component is used to display the current time.

Listing 5: clock.html The Clock Composite Component

1.  <!DOCTYPE html>
2.
3.  <html xmlns="http://www.w3.org/1999/xhtml"
4.       xmlns:h="http://java.sun.com/jsf/html"
5.       xmlns:f="http://java.sun.com/jsf/core"
6.       xmlns:ui="http://java.sun.com/jsf/facelets"
7.       xmlns:composite="http://java.sun.com/jsf/composite">
8.
9.    <composite:interface>
10.
11.   </composite:interface>
12.
13.   <composite:implementation>
14.      #{timeBean.time}
15.     <div id="clock" style="font-size: 20px; border:2px solid blue"/>
16.     <h:outputScript name="js/app.js" target="head"/>
17.
18.   </composite:implementation>
19.
20. </html>

 

Lines 4 – 7 :
Namespace declarations

Lines 9 – 11 :
Notice this component has no attributes – which is perfectly fine.
If we wanted to make this clock component fancier we could, for
example, define an attribute to indicate that our clock could
display in analog or digital mode.

Lines 13
– 18:
The implementation of our component. On line 14, we
are using the Expression Language (EL) to invoke a method on a
managed bean to start the clock ticking. The managed bean could
just utilize a
java.util.Timer method to compose and send the current date/time string
back to the client. Line 15 is a placeholder for where we will
display the
time event data from
the server. On Line 16, we load a JavaScript file that contains
application callback functions that will process the event data
returned from the server. Let’s take a look at the relevant section
of code from this application JavaScript file for this clock
component.

Listing 6: Partial JavaScript
Listing for Stock Ticker Component

1.  /**
2.   * Clock message handling function. 
3.   */
4.  function clockHandler(event) {
5.      var eventData = event.data.toString().replace(/^s*/,"").replace(/s*$/,"");
6.      var clock = document.getElementById("clock");
7.      clock.innerHTML = eventData;
8.  }

Line 5: We obtain the event data

Line 6: We obtain
our div placeholder element (created in Error: Reference source not
found, line 15).

Line 7: We write
the event data string (which is the current data / time)

 
 

The Stock Composite Component

Listing 7: stock.hmtl : Stock Ticket Composite Component

 

1.  <!DOCTYPE html>
2.
3.  <html xmlns="http://www.w3.org/1999/xhtml"
4.    xmlns:h="http://java.sun.com/jsf/html"
5.    xmlns:f="http://java.sun.com/jsf/core"
6.    xmlns:ui="http://java.sun.com/jsf/facelets"
7.    xmlns:ez="http://java.sun.com/jsf/composite/stockinfo"
8.    xmlns:composite="http://java.sun.com/jsf/composite">
9.
10.  <composite:interface>
11.
12.  </composite:interface>
13.
14.  <composite:implementation>
15.    <br/>
16.    <h:panelGrid columns="4">
17.       <h:outputText value="Space delimited symbol(s):" style="font-size: 18px;"/>
18.       <h:inputText id="symbol" value="#{stockTickerBean.symbols}"/>
19.       <h:commandButton id="submit" value="Get Quotes" action="#{stockTickerBean.getStockInfo}">
20.          <f:ajax execute="@this symbol"/>
21.       </h:commandButton>
22.       <h:commandButton id="clear" value="Reset" action="#{stockTickerBean.reset}">
23.          <f:ajax execute="@this symbol" render="symbol"/>
24.       </h:commandButton>
25.    </h:panelGrid>
26.
27.    <br/>
28.
29.    <div id="stockInfo" style="font-size: 16px;">
30.       <table id="stockTable" border="8" bordercolor="#000080" cellspacing="10" style="visibility:hidden">
31.          <tr>
32.             <th style="background-color:#b0c4de;text-align:left">Symbol</th><th style="background-color:#b0c4de;text-align:left">Open</th>
33.             <th style="background-color:#b0c4de;text-align:left">Trade</th><th style="background-color:#b0c4de;text-align:left">Chg</th>
34.          </tr>
35.        </table>
36.    </div>
37.
38.    <br/>
39.
40.    <ez:rss symbols="#{stockTickerBean.symbols}"/>
41.
42.    <h:outputScript name="js/app.js" target="head"/>
43.
44.  </composite:implementation>
45.
46. </html>

 

This composite component’s
implementation uses standard JSF components.

Line 18: The input
text field that will store the entered stock symbol data to a
managed bean.

Lines 19
– 21:
The button that, when activated, will fire a request
over Ajax to invoke a method
getStockInfo
on a managed bean. This method could utilize
a
java.util.Timer method to
reach out to a stock quote service periodically, and send the event
data back to the client.

Lines 29 – 36:
This is the placeholder area for our dynamic table to display the
stock data from the server.

Line
40:
This component uses the
rss
composite component (more about that
next).

Listing 8: Partial JavaScript Listing for Stock Ticker
Component

 

1.  /**
2.   * Server Sent Event message handling function dynamically builds a table
3.   * based on the message data. 
4.   */
5.  function stockHandler(event) {
6.      var eventData = event.data.toString().replace(/^s*/,"").replace(/s*$/,"");
7.      var stockTable = document.getElementById("stockTable");
8.      var rowCount = stockTable.rows.length - 1;
9.      if (eventData.length == 0) {
10.          stockTable.style.visibility="hidden";
11.          clearTableRows(stockTable, rowCount);
12.          return;
13.    }
14.    var quotes = eventData.split(" ");
15.    if (quotes.length !== rowCount || quotes.length == 0) {
16.         clearTableRows(stockTable, rowCount);
17.    }
18.    if (quotes !== null) {
19.         stockTable.style.visibility="visible";
20.         var newRow, newCell;
21.         for (var i = 0; i < quotes.length; i++) {
22.              var fields = quotes[i].split(":");
23.              if (document.getElementById(fields[0]) !== 'undefined' &&
24.                   document.getElementById(fields[0]) !== null) {
25.                   document.getElementById(fields[0]).innerHTML = fields[0];
26.                   document.getElementById(fields[0]+1).innerHTML = fields[1];
27.                   document.getElementById(fields[0]+2).innerHTML = fields[2];
28.                   if (fields[3] == "UP") {
29.                       document.getElementById(fields[0]+3).innerHTML =
30.                           "<img src='resources/up_g.gif'/>";
31.                   } else if (fields[3] == "DOWN") {
32.                       document.getElementById(fields[0]+3).innerHTML =
33.                           "<img src='resources/down_r.gif'/>";
34.                   } else {
35.                       document.getElementById(fields[0]+3).innerHTML = "";
36.                   }
37.               } else {
38.                   newRow = stockTable.insertRow(stockTable.rows.length);
39.                   newCell = newRow.insertCell(newRow.cells.length);
40.                   newCell.id = fields[0];
41.                   newCell.innerHTML = fields[0];
42.                   newCell.align = "center";
43.                   newCell.width = "20%";
44.                   newCell = newRow.insertCell(newRow.cells.length);
45.                   newCell.id = fields[0]+1;
46.                   newCell.innerHTML = fields[1];
47.                   newCell.align = "center";
48.                   newCell.width = "20%";
49.                   newCell = newRow.insertCell(newRow.cells.length);
50.                   newCell.id = fields[0]+2;
51.                   newCell.innerHTML = fields[2];
52.                   newCell.align = "center";
53.                   newCell.width = "20%";
54.                   newCell = newRow.insertCell(newRow.cells.length);
55.                   newCell.id = fields[0]+3;
56.                   if (fields[3] == "UP") {
57.                       document.getElementById(fields[0]+3).innerHTML =
58.                           "<img src='resources/up_g.gif'/>";
59.                   } else if (fields[3] == "DOWN") {
60.                       document.getElementById(fields[0]+3).innerHTML =
61.                           "<img src='resources/down_r.gif'/>";
62.                   } else {
63.                       document.getElementById(fields[0]+3).innerHTML = "";
64.                   }
65.
66.                   newCell.align = "center";
67.                   newCell.width = "20%";
68.            }
69.        }
70.    }
71.}

 

The stockHandler function
receives the event data, that is the stock quote information and
performs some JavaScript magic to dynamically build and display the
stock table.

The RSS Composite Component

The rss composite
component displays a link, that when activated, will dynamically
display stock company news relevant to the stock symbols that were
entered.

Listing 9: rss.xhtml : RSS
Composite Component

1.  <!DOCTYPE html>
2. 
3.  <html xmlns="http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html"
5.        xmlns:f="http://java.sun.com/jsf/core"
6.        xmlns:ui="http://java.sun.com/jsf/facelets"
7.        xmlns:composite="http://java.sun.com/jsf/composite">
8.
9.    <composite:interface>
10.       <composite:attribute name="symbols" required="true"/>
11.   </composite:interface>
12.
13.   <composite:implementation>
14.   <br/>
15.   <h:panelGrid columns="4">
16.       <h:commandLink id="submit" value="Get Stock News" action="#{rssBean.getStockNews}">
17.          <f:setPropertyActionListener value="#{cc.attributes.symbols}" target="#{rssBean.symbols}"/>
18.          <f:ajax execute="@this"/>
19.       </h:commandLink>
20.    </h:panelGrid>

21.   <br/>
22.
23.    <div id="rssInfo" style="font-size: 16px; border:2px solid blue; height : 150px; overflow : auto; visibility:hidden"/>
24.
25.    <h:outputScript name="js/app.js" target="head"/>
26.
27.
28.  </composite:implementation>
29.
30. </html>

 

Line 10: This
component defines an attribute for accepting stock symbols. The
stock symbols are used to get the rss information.

Lines 16
– 19:
The link, that when activated, will propagate the
stock symbols to a managed bean and invoke the method

getStockNews on the managed bean over
Ajax.

Line 23: A div
placeholder for displaying the returned rss information from the
server.

Listing 10: Partial JavaScript
Listing for RSS Composite Component

1.  function rssHandler(event) {
2.      var eventData = event.data.toString().replace(/^s*/,"").replace(/s*$/,"");
3.      var rssInfo = document.getElementById("rssInfo");
4.      if (eventData.length == 0) {
5.          rssInfo.style.visibility="hidden";
6.      } else {
7.          rssInfo.style.visibility="visible";
8.      }
9.      rssInfo.innerHTML="";
10.    rssInfo.innerHTML = eventData; 
11. }

 

The
rssHandler callback function writes the
rss event data into the div that was created in Error: Reference
source not found.

Server Implementation
Considerations

In the browser that has SSE
support. section, we mentioned some of the requirements for sending
SSE event data to the client. Server implementations need to
maintain an open connection to stream event data to the client. For
Java implementations, a Servlet utilizing the Servlet 3.0
Asynchronous API is one choice. For other implementations, NodeJS
or PHP could be used as well. Server implementation details are not
mentioned, since the main focus of this article is the JSF user
interface and components.

Conclusion

This article has been all about
creating JSF composite components that make up the user interface
for a stock quote application that uses HTML5 Server Sent Events.
JSF 2 introduced the world to composite components, and together
with JavaScript, provides a component author with a multitude of
options for creating dynamic components. HTML5 Server Sent Events
are the “second class citizen” to HTML5 WebSockets, but it does
offer an HTTP solution to event streaming without switching to
another protocol.

 

Roger Kitain is a Principal
Software Engineer at Oracle where he has spent most of his time in
the web technology space since 1997. Roger has been involved with
the JavaServer Faces technology since its inception, and has co-led
the JavaServer Faces Specification since JSF 1.2. He has presented
at many conferences including JAX, JSF Summit, JavaOne, Dexoxx,
Jazoon, Ajax Experience, Ajax World.  

 

This article first appeared in
Java Tech Journal : Web Frameworks. For more of that issue, click
here

 

Author
RogerKitain
Roger Kitain is a Principal Software Engineer at Oracle where he has spent most of his time in the web technology space since 1997. Roger has been involved with the JavaServer Faces technology since its inception, and has co-led the JavaServer Faces Specification since JSF 1.2. He has presented at many conferences including JAX, JSF Summit, JavaOne, Dexoxx, Jazoon, Ajax Experience, Ajax World.
Comments
comments powered by Disqus