Seam / JSF / GWT Integration: What, Why, and How
Rob Jellinghaus, rjellinghaus at
gmail dot com
Article version 0.2, 1 May 2007
This article describes the rationale behind and the implementation of a
demonstration application integrating JBoss Seam, Java Server Faces,
and the Google Web Toolkit.
This integration is very much a work in progress, but has reached a
sufficiently complete point to be worth describing in more detail.
The demo this article describes is
available here: http://unrealities.com/seamgwt
-- feel free to download it and play with it either before or after
reading this article. The build directory contains a
"jboss-seam-blog.war" file which you can drop into any Tomcat (with JDK
5; there may be an issue with JDK 6), then go to http://localhost:8080/jboss-seam-blog,
scroll to the bottom, and click "[Browse with GWT]". See the
included readme.txt for more details.
Contents:
- What?
- What
is Seam?
- What
is Java Server Faces?
- What
is the Google
Web Toolkit?
- What
is G4JSF?
- What
is this demo I wrote?
- Why?
- Why
integrate
GWT and JSF? Isn't that a bit redundant?
- Why
integrate
GWT and Seam? Aren't they solving the same
problem?
- How?
- How
does the
usual JSF / Seam request pipeline work?
- How
does the
usual GWT module loading work?
- How
did the
G4JSF integration couple GWT and JSF?
- How
did I extend
G4JSF and GWT to improve the coupling?
- How
does the
current project integrate Seam with the improved G4JSF?
- How
does this demo differ from the Seam blog demo?
- What
next?
- Acknowledgements
What?
All three of these technologies are relatively new in Java terms,
ranging in age from one to three years. The Java web technology
world is large and diverse enough now that it is very possible for
skilled developers to remain largely unfamiliar for years with
technologies they have not personally investigated. So, first, a
very brief discussion of what each of these frameworks actually
is. Please skip anything you already know :-)
What is Seam?
Seam is a server-side component framework built on EJB3. It is
the brainchild of Gavin King, the original developer of Hibernate.
Seam's primary mission is to create a dependency-injection framework
for web applications, one which efficiently manages multiple coexisting
sets of state. The main motivator behind Seam is the difficulty
of writing web server code that juggles session state, business process
state, per-user-interaction state, page state, and so forth. Seam
leverages Java 5 annotations to allow easy construction of Java
components (classes) that, by using annotations, declare their mutual
dependencies and their state management needs. Each set of state
managed by Seam is termed a context,
and hence Seam is a framework for contextual
components.
For example, one of the main Seam contexts is the conversation. A
conversation is a thread of interaction with a given
user. A given user may have multiple conversations active with a
single web application (for instance, they may have multiple browser
windows open, making multiple reservations in a booking system).
Tracking state for these multiple interactions is normally extremely
tedious and error-prone. Seam, by implementing this state
tracking in the framework, makes it nearly automatic. Seam
focuses on state management for the business logic components of web
applications.
More generally, Seam has rich support for interacting with EJB3
persistence -- transaction handling, tracking objects across
multiple web requests, and similar problems are handled by the Seam
framework. Finally, Seam has a large and growing component
library, for PDF rendering, email handling, and much more.
What is Java Server Faces?
Java Server Faces is Sun's latest attempt (and a largely successful
one) to standardize on a component framework for building Web user
interfaces. Whereas Seam is primarily for building business logic
components, JSF is for building web user interface components.
The fundamental JSF interface model is a tree of user interface
components. Each JSF component is responsible for its own
rendering and event handling. A web request to a JSF application
passes through a carefully structured series of phases, involving
request parsing, parameter validation, event propagation, component
update, and finally view rendering. JSF provides a very
extensible framework, allowing the request pipeline to be intercepted
very flexibly, and allowing many kinds of custom components to be
written.
JSF supports multiple view frameworks for describing JSF pages and
rendering output. In practice, two dominate: JSP and Facelets. JSP is
considered legacy technology and its uneasy mix of Java and declarative
XML is mostly deprecated. Facelets is a much cleaner
implementation of an XML-based set of tags for creating a JSF component
tree. Facelets supports the JSF
expression language to allow straightforward, declarative access to
the state of server objects within a view.
Seam in many ways is specifically designed to augment JSF; it
facilitates writing JSF actions, it provides support for REST-style
pages within JSF, and much, much more.
One of the more ambitious examples of JSF component development is the JBoss Ajax4JSF project.
The Ajax4JSF project (and similar JSF extension projects) implement JSF
components that essentially leverage the JSF component tree for partial
rendering. The normal JSF request cycle is a full page render,
but Ajax4JSF short-circuits this to allow subtrees of the overall
interface tree to be dynamically re-rendered via AJAX requests.
This basically allows one to write a JSF page using Facelets tags, but
to get a very flexibly interactive AJAX page as a result, without
actually needing to write Javascript.
What is the Google Web
Toolkit?
The Google Web Toolkit is an attempt to raise the language level of
client-side web applications development. The GWT developers felt
that building AJAX applications was excessively difficult, given their
experience with Java. Javascript is not a compile-time language,
so type errors are all too common. Web browsers lack great
debugging support, so tracing through a Javascript application is
tedious compared to a Java application. Javascript provides no
good support for partial loading, which means that using a Javascript
library -- even if only a small portion of it is actually needed by
your application -- imposes a large and fixed download cost on the
user. Transmitting data to AJAX applications from a server-side
Java application can be tedious; it requires finicky
serialization. Finally, different browsers have different DOM and
Javascript idiosyncrasies, making portability painful.
GWT's solution to these problems is to implement a very sophisticated
Java-to-Javascript compiler. A GWT application is written in
Java. GWT defines an extensive library of user interface
controls, rather Swing-like or SWT-like in API structure. An
individual GWT application uses these components very much like a Swing
app would. Server communication is done by asynchronous
RPC. GWT applications are packaged into components called modules; a module defines a set of
source (client source and server-only source), along with HTML, CSS,
and other resources needed for rendering.
The Java source of a GWT application can then be compiled to Javascript
and executed in the browser; GWT's term for this is "web mode".
The compilation process carefully strips out all code that is not
actually used at runtime, guaranteeing minimal script download
sizes. The compiler also generates multiple versions of the
Javascript, each one tuned to the peculiarities of a particular
browser; at runtime, browser sniffing loads the appropriate customized
Javascript. Built-in RPC libraries enable Java objects to be
serialized over the wire with minimal difficulty; GWT implements its
own serialization protocol. All GWT applications are deployed to
production in "web mode"; at runtime, the browser only ever sees
Javascript.
Since a GWT application can be very complex, yet keep all its state
client-side, GWT apps can be more scalable than applications built with
frameworks that maintain more server-side state.
Finally, GWT tackles the client-side debugging problem with its best
magic trick: "hosted mode". In hosted mode, GWT launches a
JVM which uses JNI to instantiate a browser. The JVM then
executes the
Java code of your application,
directly, and passes all DOM manipulations to the browser through
JNI. This
is AJAX, only the J stands for Java, not Javascript. You can run
your favorite
Java debugger and step through your Java program effectively running in
the local browser. This lets developers leverage the full Java
toolset for client-side Web application development.
The principal downside of GWT is that its applications are entirely AJAX. Rendering
static content (for searchability, for example) is not possible with
GWT. GWT in this regard is no different than any other AJAX
framework, but because GWT makes it so straightforward to build large
applications, the tendency is often to build everything in GWT.
This can make interoperation with other web frameworks, and
implementing non-AJAX-friendly site features, relatively more difficult.
What is G4JSF?
Since JSF is such an extensible component framework, and since GWT is
such a compelling tool, the Ajax4JSF team in late 2006 created the
first version of the G4JSF
integration kit.
Simply, the G4JSF library supports building custom JSF components that
encapsulate GWT modules. A GWT application can be packaged into a
JSF component and installed in a JSF application. This
immediately provides a powerful and flexible bridge from the JSF world
to the GWT world. Now web applications can be built using a JSF
overall structure for navigation and for non-AJAX-friendly pages, and
using GWT for handling more intensively interactive site features.
What is this demo I wrote?
The Seam distribution comes with several examples of different
mini-applications, including a reservations system, a DVD store,
etc. One of the examples is a blogging application, which
demonstrates REST-style bookmarkable URLs for blog posts, multiple
themes through CSS selection, wiki-style markup, and conversational
post editing (with login checking). The server-side code is very
minimal; each server-side component is less than a page, and there are
only about half a dozen such components.
I took this blog example and extended it with a GWT module which allows
AJAX-style browsing of posts. The GWT module makes RPC calls to a
bridge Seam component, which uses the other Seam components for all
persistent storage and retrieval of blog posts. The GWT module is
encapsulated in a JSF component, which fits neatly into the
pre-existing Seam JSF application.
Why?
This next section discusses the rationales for taking on this project
at all. "Because it's there!" is only partly convincing :-)
Why integrate
GWT and JSF? Isn't that a bit redundant?
Quite a few people on the GWT user group are of the opinion that GWT
makes JSF seem clunky and poorly-performing by comparison. They
question what value JSF has if GWT enables construction of
sophisticated, scalable applications. Conversely, quite a few JSF
people -- especially users of AJAX-enabled
JSF component libraries -- question the value of GWT.
There clearly is a lot of overlap between the functionalities of GWT
and those of JSF. Quite a few successful applications have been
built with just one framework or the other. So clearly
integrating the two is not necessary.
But based on my experience so far, the really colossal benefit of using
GWT with JSF is this: it makes it
really easy and fun to add your own AJAX code to your JSF application.
The native JSF pipeline is very complex and building AJAX JSF
components requires a lot of JSF expertise. But building GWT
components that run in your JSF page is a piece of cake by comparison
(if you use this integration library!),
and scales up gracefully to as much functionality as you could want.
Integrating GWT into JSF gives you a huge toolbox. You can write
individual GWT widgets that do one small job and do it well. Or
you
can take a big chunk of your page and build an entire mini-application
in GWT. Both fit equally nicely into the overall structure of
your JSF
application.
Why integrate
GWT and Seam? Aren't they solving the same problem?
A case could be made that GWT and Seam are also
overlapping technologies. Specifically, they both provide (very
different) solutions to the problem of conversation state.
In GWT, all state is purely client-side. A GWT application could
be opened multiple times in multiple browsers, and each window would
essentially have its own instantiation of the application. Hence
each window could be in any state the user wishes, and there is no
state conflict. More, the server need not even be aware of the
number of client windows that are open; each window communicates with
largely stateless server services, and the server is not tasked with
tracking more state due to more interactions with a single
client. (GWT, and Google, best practice is to build server
services as statelessly as possible, for maximal scalability.)
In Seam, conversational state is tracked by the server. This
imposes a certain scalability cost, but for many applications this is
not infeasible. And Seam's server-side state tracking carries
real benefits. The ability to track persistence contexts relative
to client interactions can lead to more optimized database usage.
Extended transactions can be easily tracked and cleaned up. It is
much more convenient to load persistent state as necessary -- there's
no need to manage the scope or consistency of the data sent over the
wire.
So when addressing the problems of conversations, it's clear that there
is a lot of functional similarity between GWT and Seam, even though
each has its pros and cons.
But there is more to Seam than conversation handling. Seam
also supports business process tracking, page-scoped request
parameters, PDF generation, dependency injection, easy persistence and
transaction handling, and a wide variety of other features that are
definitely useful for implementing the server side of any Internet
application -- even one whose client side is entirely GWT. In
fact, Michael Neale of Red Hat has implemented a "remoting-style"
integration of GWT and Seam, allowing a GWT application to invoke Seam
components without any JSF being involved at all.
So perhaps rather than focusing on the overlap between GWT and Seam, we
should focus on their differences, and let those guide us towards
leveraging each to fullest effect.
How?
Now, finally, time to talk about actual code! I apologize if this
next section is dense; it's very hard to know how much explanatory
detail to provide to an audience with widely varying experience levels
in each of these frameworks.
How does the
usual JSF / Seam request pipeline work?
JSF describes a multi-step request pipeline with the following phases:
- RESTORE_VIEW - Reconstruct the JSF component tree at start of a
new request.
- APPLY_REQUEST_VALUES - Extract parameters from the request and
apply to appropriate components.
- PROCESS_VALIDATIONS - Validate components to ensure that
parameters are acceptable.
- UPDATE_MODEL_VALUES - Update components with the validated
parameters.
- INVOKE_APPLICATION - Invoke any actual server-side actions.
- RENDER_RESPONSE - Traverse the compnent tree, rendering all view
components.
JSF allows applications to install phase
listeners -- objects that receive notifications at one or more
stages of the JSF request pipeline. Seam defines a set of phase
listeners (for different types of Seam applications, e.g. transactional
Seam apps, portal-based Seam apps, etc.). These listeners track
each phase, and constructs or reloads Seam contexts as necessary.
For example, the TransactionalSeamPhaseListener opens a database
transaction just before the RESTORE_VIEW phase (so that if the view
accesses unloaded persistent state of any objects held in the session,
conversation, or page contexts, that state can be loaded). It
commits the transaction after the INVOKE_APPLICATION phase.
The other main coupling between JSF and Seam is at the component
level. A JSF component -- say, a text field-- may specify,
through the JSF expression language, a particular object and field to
pull data from. For example:
<h:inputText value="#{blogEntry.title}" size="70" maxlength="70" required="true" id="title"/>
This specifies that the inputText component gets its initial value from
the title field of the blogEntry component. And more, if the user
changes the title by typing into the field, that new title will be
injected back into the same component when the edited post is submitted.
Seam makes all its named components visible to the JSF expression
language. Hence, all JSF components that pull/push data to -- or
that invoke actions on -- JSF server components can automatically reach
the Seam components.
So much for JSF and Seam. On to GWT.
How does the
usual GWT module loading work?
GWT has to do some interesting tricks when loading a page containing a
GWT module. Specifically, it has to:
- load a "selection script" for each module
- the "selection script" has to sniff the browser and then load the
browser-specific script for that module
- finally, the browser-specific module script has to run
It starts this off with the following basic page pattern (from the GWT "Kitchen
Sink" sample):
<html>
<head>
<title>Kitchen Sink</title>
</head>
<body>
<!-- This script is the bootstrap stuff that simply must be there; it is sent down uncompressed -->
<script language='javascript'
src='com.google.gwt.sample.kitchensink.KitchenSink.nocache.html'></script>
<iframe id='__gwt_historyFrame' style='width:0;height:0;border:0'></iframe>
</body>
</html>
The <script> here is the selection script. The selection
script sniffs the browser, and then picks a particular version of the
actual module script to load. (The module scripts have
hexadecimal names such as C0C67D86316F36DDCAA0894130982AFA.cache.html
-- these names are generated by hashing the module script itself.
This permits maximal caching of module scripts, while supporting
rebuilds and redeployments of the modules themselves.)
Once the actual module script loads, the selection script (which has
been checking Javascript sentinel variables to detect module loading)
calls the module's "entry point" (rather analogous to main()), and
finally the actual GWT code gets to run.
So, ultimately, running a GWT module requires two things:
- the proper script tag in the body of the page
- the proper scripts located in the web application at the expected
places.
How did the
G4JSF integration couple GWT and JSF?
Given that the whole point of G4JSF is to make including a GWT module
pretty much just like using a normal JSF tag, how exactly does it make
this magic happen? For example, in the current demo, the JSF code
to render a GWT list of blog posts looks like this (from
gwtBrowse.xhtml):
<bloglist:component id="main2">
<gwt:gwtListener serviceBean="#{gwtBlog}"/>
</bloglist:component>
How can this be made to work?
Well, by itself, it can't, quite. The main tags that G4JSF
defines are fundamentally <gwt:page>, which essentially replaces
the outermost <html> tag in a Facelets tree, and
<COMPONENT-NAME:component>, the GWT-module-specific JSF component
tag.
The outermost <gwt:page> tag looks like this (from template.xhtml
in the demo):
<gwt:page id="pgHome" title="#{blog.name}">
<f:facet name="head">
<h:panelGroup>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>#{blog.name}</title>
<link href="#{theme.css}" rel="stylesheet" type="text/css" />
</h:panelGroup>
</f:facet>
...
</gwt:page>
This <gwt:page> tag has sub-facets that allow specific elements
to be injected into the <head> of the rendered HTML.
<gwt:page>'s main job is to survey the JSF component tree,
looking for GWT components. Any GWT components it finds get
<script> tags injected at the very top of the <body> (where
they have to be, to allow them to run first).
<gwt:page> also injects <meta> tags into the header that
provide some critical JSF data, including the postback URL for the
server, the server's view ID, and the base URL for scripts located on
that page.
Then the actual <bloglist:component> tag defines the location at
which that particular module runs its entry point; it basically sets
the location in the DOM for the module to work its AJAX will.
Another problem is fetching the GWT resources. How is that done,
given that now we're in the JSF pipeline? The G4JSF project
defines its own phase
listener, which intercepts incoming requests for GWT scripts / pages /
resources and returns them directly.
Finally, what about connecting to the server? How does the GWT
module, ensconced in the JSF component, know how to get to the
server? It constructs a service endpoint URL as follows (gluing
together code from ComponentEntryPoint.java and
BlogListWidgetEntryPoint.java):
String action = getMetaProperty("action");
String endpoint = action + "?javax.faces.ViewState="
+ viewState + "&x-gwtcallingmodule="+GWT.getModuleName();
if (null != clientId) {
endpoint = endpoint + "&clientId=" + clientId;
}
((ServiceDefTarget) service).setServiceEntryPoint(endpoint);
return service;
So it finds the base server URL from the <meta> parameter
injected by <gwt:page>. It then constructs its service URL
by including the JSF view state and the GWT module name, and also
including the specific ID of the component itself. When the GWT
module makes an RPC request to this URL, the server-side
GwtPhaseListener intercepts the request in the INVOKE_APPLICATION
phase, and routes it to the right place.
The specific component ID is in turn used on the server side to help
the server dispatch the request. Above, we had this component:
<bloglist:component id="main2">
<gwt:gwtListener serviceBean="#{gwtBlog}"/>
</bloglist:component>
The server knows that the GWT module with id "main2" has a gwtListener
that is connected to the gwtBlog component on the server. It uses
that information when routing RPC requests. The actual lookup of
the gwtBlog component is done through JSF's normal expression language
resolution, which, as we saw above, is wired into Seam.
This is one area where JSF may allow more flexible routing of GWT
service requests than GWT itself by default supports.
How did I extend
G4JSF and GWT to improve the coupling?
Actually, the above code wasn't originally how G4JSF worked. When
G4JSF was first implemented in August of 2006, GWT's RPC implementation
was excessively hard-coded. Basically, the only way to implement
a GWT RPC service was to subclass the GWT RemoteServiceServlet with
your own servlet, which implemented your service interface.
Something like this:
public class MyServiceServlet extends RemoteServiceServlet implements MyServiceInterface {
public void doMyServiceThing (String myServiceArgument) { ... }
}
But this wasn't really very well aligned with the goal of the JSF
integration, which was to let JSF do the job of gluing any arbitrary
GWT module together with any arbitrary server-side component that
happened to support the same interface. Ideally the G4JSF bridge
would not need any interface-specific code; the plug-in GWT module
should define both the client-side code and the service interface, and
that server-side interface could be implemented by any number of
server-side components. But given the original GWT RPC
implementation, each new service interface required a new servlet
implementation.
G4JSF avoided the issue by defining a simple, generic,
event-passing-style service interface:
public interface GwtFacesService extends RemoteService {
public GwtFacesResult sendEvent(GwtFacesEvent event)
throws GwtFacesException;
}
This was fundamentally the only
service that G4JSF modules could call.
I started spinning up on G4JSF in November 2006, and immediately ran
into this issue, and was confounded. Surely not! The beauty
of GWT was that it supports rich RPC to arbitrary service
interfaces. Why should this be discarded? It felt like it
ought to be possible to fix things so that a G4JSF module could define
client code and a service interface, and then that could be routed to,
yes, a service-interface-implementing Seam component.
Then GWT was open sourced in December 2006, and suddenly anything was
possible!
There was an existing issue for the RPC restrictions in GWT. I
took on creating a GWT patch in January 2007, leading to an awful lot
of very productive work on
the GWT forum and in
the GWT issue tracker. Ultimately this was landed for GWT 1.4
(release coming Real Soon Now, preview available already). A nice
explanation of the new RPC structure is here.
Once the GWT RPC mechanism was properly routable, I refactored the
G4JSF integration library to leverage it. It then became possible
to define gwtListeners on GWT modules that could route to arbitrary
beans, bringing us up to the present day. Some other
modifications were necessary due to bootstrapping changes in GWT
1.4. I will be landing these G4JSF modifications in the Ajax4JSF
trunk as soon as GWT 1.4 final ships.
How does the
current project integrate Seam with the improved G4JSF?
So, are we done? No, not quite. Out of the box, the
GwtPhaseListener and the SeamPhaseListener don't quite get along.
The root problem is that the GWT phase listener has to go first -- if
it is returning a GWT resource, then it wants to do so before the Seam
listener opens a transaction; and if it is invoking a service method,
it wants to do so before the Seam listener closes a transaction. But the
JSF specification doesn't define any way to
sequence multiple PhaseListeners.
The solution is fairly simple (comments and some code elided):
public abstract class AbstractGwtSeamPhaseListener implements PhaseListener {
private PhaseListener gwtPhaseListener = new GwtPhaseListener();
protected abstract PhaseListener getSeamPhaseListener();
protected PhaseListener seamPhaseListener;
public void beforePhase(PhaseEvent event) {
if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
gwtPhaseListener.beforePhase(event);
}
// Call the SeamPhaseListener beforePhase()
getSeamPhaseListener().beforePhase(event);
}
public void afterPhase(PhaseEvent event) {
if (event.getPhaseId() == PhaseId.INVOKE_APPLICATION) {
gwtPhaseListener.afterPhase(event);
}
getSeamPhaseListener().afterPhase(event);
}
}
Basically, make a composite PhaseListener that calls the two
frameworks' listeners in the proper order in each phase. (Thanks
to Jason DeTiberus for this basic idea.)
The only remaining problem is that the GWT compiler doesn't support
Java 5 syntax. So how can we possibly pass Seam entities in RPC
calls? Obviously that's what you want to do, but it's not really
possible. And even if the GWT compiler DID support Java 5 syntax,
it will not necessarily be able to handle all of the imports and other
annotations referenced by typical Seam entities.
What's needed is some kind of data transfer object layer, to allow
serializing just the data over to the relatively limited codebase on
the client side. But data transfer objects are notoriously
tedious to build.
So the current demo implements a very lightweight data transfer object
class generator, based on the JAM
subproject of Codehaus AnnoGen. This simply walks over the
classes in a package, emitting DTO versions of them. It maps
associations into the DTO hierarchy (so if domain.BlogEntry has a field
"domain.Blog blog", then dto.domain.BlogEntry will have a field
"dto.domain.Blog blog"). And it omits fields that the GWT
compiler can't handle. Soon there will be a small amount of
reflective glue to automatically populate DTO graphs over to the
client, and then most of the boilerplate will be handled.
How does this
demo differ from the Seam blog demo?
Functionally, I added a new view that renders the contents of the blog
as an AJAX tree. You can expand the tree to read an entry.
The tree
fetches the formatted HTML for each post asynchronously from the
server. At the bottom of each expanded entry is an "Edit this
post"
button; when clicked, this gives you a text area where you can edit the
wiki markup of the post. Another button click submits the post
and
updates the tree view. Each post also has a bookmarkable link,
just as
in the main demo.
Adding this AJAX functionality took only a bit over two pages of GWT
code (including comments and imports). Here, for example, is the
code
that implements the "Edit this post" button:
final Button editButton = new Button("Edit this post");
editButton.addClickListener(new ClickListener() {
private boolean editing = false;
public void onClick (Widget sender) {
if (!editing) {
editArea.setVisible(true);
editButton.setText("Submit this edit");
editing = true;
} else {
// time to submit!
editArea.setVisible(false);
editButton.setText("Edit this post");
entry.setBody(editArea.getText());
fetchFormattedText(formattedText, entry);
blogService.updateBody(entry.getId(), entry.getBody(), new AsyncCallback() {
public void onSuccess (Object result) {}
public void onFailure (Throwable caught) {
status.setText(status.getText() + "\nError: " + caught.getMessage());
}
});
editing = false;
}
}
});
editPanel.add(editButton);
What could be simpler than that? Here is the Seam method that the
above code calls:
public void updateBody (String id, String body) {
blog.getBlogEntry(id).setBody(body);
entityManager.persist(blog.getBlogEntry(id));
try {
pojoCache.remove("pageFragments", "index");
} catch (CacheException e) {
throw new RuntimeException("Could not clear pojoCache", e);
}
}
This code was pulled directly out of other existing Seam methods.
If I
were more clueful about Seam I could have done it with even less code.
The amazing thing about this stack is that once the JSF plumbing is
sorted out, you really can spend most of your time writing plain Java
-- either client-side GWT Java, or server-side Seam Java. Both
GWT and
Seam are really pleasant to program in, given their rich APIs and
consistent support of the programmer.
As for the overall code changes: The "[Browse with GWT]" link at
the
bottom of the main demo page was added to nav.xhtml, and references a
new view, gwtbrowse.xhtml. This view pretty much consists only of
a
single <bloglist:component> tag, which designates the "gwtBlog"
component as its service.
The gwtBlog component is a new component of class GwtBlogService, which
has four simple methods: getBlog, getBlogEntries,
getFormattedText,
and updateBody (see above). It is hardly worth saying more about
it,
except that it implements the "seamdemo.blog.client.BlogService"
interface, which is actually defined in the
seamdemo.blog.BlogListWidget GWT module.
That's pretty much it for the changes to the Seam codebase. The
rest
of the changes are the code for the GWT modules themselves, the DTO
generator, and the related build infrastructure.
There are actually two GWT modules in this demo; one of them,
demo.gwt.HelloWidget, is taken from the a4j-gwtKickStart sample that
shipped with G4JSF originally (you can restore this one by commenting
it back in at the top of index.html). The other one,
seamdemo.blog.BlogListWidget, was created by copying the
demo.gwt.HelloWidget directory and search-and-replacing the module and
class names.
There is a fair amount of JSF boilerplate, but if you compare it
between the two modules, you will see that virtually none of it had to
be touched up (other than search-and-replace) to create the new
module. In fact, the original
G4JSF
project had an Ant target to generate all the boilerplate; I will try
to revive this in the near future.
The build.xml file goes through the following stages when building:
- Compile the DTO class generator.
- Generate the DTO source classes.
- Compile the Seam demo code (which references the DTO classes, and
which references an interface defined in the GWT module).
- Assemble the jboss-seam-blog.war file.
- Compile the Java code of the GWT modules into Java classes.
Put
the resulting Javascript and HTML into per-module build subdirectories.
- (A macrodef makes this reasonably clean; adding more GWT
modules to the project would entail only adding a couple of macro calls
per module.)
- Compile the Java source into Javascript, using the GWT
compiler.
Put the resulting Javascript and HTML in per-module build
subdirectories.
- Assemble module JARs, one per module. Each module JAR
contains the entire module -- all scripts, HTML, CSS, the works.
- Repackage the main jboss-seam-blog.war, adding the module JAR
files.
The upshot is that the module compilation is pretty much totally
separate from the Seam build, and the modules themselves are totally
separate from each other. So adding more modules would be quite
straightforward and clean in the build.
What next?
The next steps for this overall project are:
- Possibly iterate the demo and/or this article a bit more before
JavaOne.
- Attend the JavaOne
2007 Seam BOF (Tuesday night, May 8th, 9 pm), to
discuss this with anyone interested.
- Attend Google's
Mountain View developer day (May 31st), again to discuss with
anyone interested.
- Once the final version of GWT 1.4 ships, get the current G4JSF
code building under Maven in the Ajax4JSF trunk, and then submit my
G4JSF patches to the Ajax4JSF project.
- Once the final G4JSF patches are landed, submit this overall demo
as an example for the Seam distribution. Hopefully they will
accept it!
Acknowledgements
Five months ago I knew very little about any of these frameworks
(except for Seam). The work here benefited hugely from assistance
from the following people:
- George Georgovassilis created GWT
issue 389 which made it clear that I wasn't the only one interested
in refactoring GWT's RPC.
- Miguel Mendez at Google was a critical collaborator on the GWT
RPC changes, which in the end are as much his as mine.
- Bruce Johnson at Google did a diligent and painstaking review of
the final patch.
- Robert Hanson wrote a great blog post
explaining the new RPC mechanism.
- The original G4JSF integration was mostly written by Alexandr
Smirnov at Exadel; he did the vast majority of the hard work, and
without his JSF and GWT insight this project would never have happened.
- Sergey Smirnov and Igor Shabalov at Exadel were very welcoming of
my interest in this integration, and freely granted me committer rights
at JBoss.
- Jason DeTiberus provided some key insights about the conflicts
between the GWT and Seam phase listeners, saving me from an awful lot
of frustration.
- Michael Neale at Red Hat expressed early interest in this project
and has implemented his own "remoting-style" Seam-GWT integration
(leaving JSF out of the
picture).
- Michael Yuan at JBoss liked the demo enough to want to show it at
JavaOne 2007, spurring me to write this article quick fast in a hurry.
- And finally, my wife Michelle has supported me night after night as
I hacked away. Without her, and her care for our daughter Sophie, I'd
never have gotten this done.
Onwards!
Cheers,
Rob Jellinghaus
rjellinghaus at gmail dot com