Dashboard > SakaiPedia > Gradebook - Taming JSF 1.1
  SakaiPedia Log In | Signup View a printable version of the current page.  
  Gradebook - Taming JSF 1.1
Added by Ray Davis, last edited by Ray Davis on Jul 19, 2005  (view change)
Labels: 
(None)

Taming JSF 1.1

Ray Davis - University of California, Berkeley

Background

In summer 2004, Josh Holtzman and I produced a pilot online gradebook for UC Berkeley. One goal was to test our interface assumptions and integration approach against real-world data. Another goal was to gain experience with the foundation technologies that were being used on the Sakai project, three of which were very new at the time:

  • The back end: Hibernate 2 for mapping Java objects and relational databases
  • The business logic: Spring Framework 1.0 for integrating business components in a lightweight fashion
  • The front end: Java Server Faces 1.0 for the presentation layer

Our experience left us very happy with Spring, moderately happy with Hibernate, and not at all happy with JSF. We did manage to deliver a Pilot Gradebook that told us what we needed to know, but sacrificed reliability, consistency, and scalability to do so.

In January 2005, when we moved to full-time work on the official Sakai 2.0 Gradebook, JSF was our biggest concern.

First, JSF 1.1 lacks UI components (tags, widgets). The premise accompanying JSF 1.0 was that big Java companies would provide rich libraries of UI components through their commercial development products, and happy designer drones would simply glue these together. But what happens if you don't have access to commercial libraries? And what were developers supposed to do in the meantime?

Even allowing for the limitations of the JSF 1.1 specification, Sun's implementation had bugs which may not have been obvious in simple test programs but which proved difficult to work around as the UI became more complex.

Worst of all, JSF 1.1 offers no fallback. Mixing JSF and JSTL is problematic, to say the least. In the Pilot Gradebook, when we resorted to JSTL-based tags for lack of JSF functionality, we created a series of bugs, some blatant, some lurking.

As I'll explain later, JSF is also more difficult to write new tags for than alternatives like JSP 2.

JSF 1.2 will be compatible with JSP 2, allowing for more rapid incremental development, and targets many of the practical problems of JSF 1.1 in the web environment. But JSF 1.2 isn't available yet.

Berkeley Pilot Experience

Last year, mimicking what sample code we could find left us mired in architectural and usability problems.

That sample code tended to emphasize JSF's "pull" approach to MVC: backing beans concentrating on business logic rather than slavishly modeling individual web pages, and JSF tags and backing beans both making direct reference to domain objects. That emphasis was understandable in a way, since it underlined a key difference between JSF and Struts. Practically, it proved unfortunate.

For example, backing beans would do database queries when a getter hits a null value. In practice, that meant we couldn't really be sure when the queries were being done or to what purpose. Some DB queries were issued in the Apply Request Values phase, some in the Process Validations phase, and some in the Render Response phase. We'd unexpectedly display old data instead of freshening from the DB. Or we'd add a "rendered" check to a button and find that submits had stopped working.

We'd start off assuming that individual pages shared a great deal of underlying logic, have them share backing beans, and then see the bean code slowly become cluttered with page-specific logic.

Over and over again, we found ourselves unable to maintain request-to-request page states in what seemed like a "normal" web way, and were forced to move beans to session scope. I'll explain the consequences a bit later.

We didn't want to deliver the same problems with the official Sakai 2.0 Gradebook. How would we develop quickly and safely with JSF 1.1?

Taming the Bugginess

Here, the best solution we found was to switch to MyFaces.

Not that MyFaces had any shortage of bugs, but Sun's implementation is handicapped by an awkward release cycle. Sun's next JSF version is tied to the massive J2EE 5.0 release. MyFaces, being open source and independent of other projects, can update more frequently and address issues with more agility. We've found the MyFaces team to be responsive, if weak on documentation. When it was needed to get around a problem, we had access to the CVS head.

The final version of the Gradebook does, in fact, run on either Sun's implementation or MyFaces' implementation of the JSF specification. But development went faster on MyFaces.

The Scope Issue

The problem that concerned me most at the start of the project was the scope of our JSF backing beans.

The web is request-driven, but application workflows require continuity. Dealing with that conflict is a big part of any presentation framework.

In pure request scope, we have "One page" = "One logical context". There are no extended transactions. The browser asks for something, it gets it, and the contract is complete.

In pure session scope, from the time a user enters the site to the time the browser is closed (or the session times out), every interaction takes place in one big context.

Programmers sometimes like this idea because it seems as if it magically turns the browser into a desktop application. Not too surprisingly, the problem is that it's not very weblike, which leads to difficulties for both web users and web servers.

  • Loss of normal browser functionality - If you keep page state in a session-wide context, users can no longer take advantage of many of the usual benefits of a browser-driven web application, such as back buttons, multiple data views (one browser window shows how Section 1 is doing, another browser window shows Section 2, and the user compares the two) and "what if?" or preview support.
  • Stale data - The developer takes on the problem of synchronizing the shared database with long-lived objects in user-specific memory. If you have fairly complex objects and pages, you also often take on the problem of synchronizing slightly different in-memory views of the same underlying data. Since session-scoped data doesn't automatically disappear at the end of a request thread, by default that data hangs around until the session dies. In the Pilot, we had to try to explicitly clean it up on every exit path.
  • Extra memory consumption - Since session-scoped data is generally maintained by the server, memory consumption increases and scalability decreases as new views are moved into session scope.

What most of us picture as ideally happening when we interact with a web application is something not quite "pure request" or "pure session", which we'll call "request thread" scope.

This is something that people are just starting to feel a need to name. Oracle's ADF Faces call it "page flow", but I've also heard it called "task scope" and "workflow scope".

In this scenario, you carry the context from request to request when it makes sense to. Out-of-date junk from old requests isn't kept in server memory. Instead, the context is driven by the browser itself. The user can start a new thread off at any point, and it'll be independent of other threads except for sharing the underlying database and user identification.

For us, the Holy Grail was to support request threads in JSF.

Request Threads in JSF

What finally clued us in was the popularity of the MyFaces "x:saveState" tag. There is something that JSF maintains pretty religiously from request to request: the JSF component states. The trick to getting request thread support was to piggyback on a component with everything needed to coordinate the data model and the user view.

We wrote an invisible JSF component called FlowState to glue the JSP page and the backing bean together. This component lets backing beans track the request life cycle and save necessary state within the component tree itself. Like the MyFaces x:saveState tag, it passes a serialized object from request to request without use of session scope. That in turn enables request-thread functionality such as "what if?" scenarios and multiple active application views from a single session.

<gbx:flowState bean="#{phaseAwareBean}" />

The tag goes in the JSP file before any other bean references are made. Any non-transient fields in the bean will then be saved and restored from the component before other rendering logic kicks in.

JSF Data Synchronization

Now that we had a request thread state, we needed to merge with the current state of the database in a controlled fashion. That's done with phase-aware backing beans.

The FlowState component uses the backing bean's PhaseAware interface to alert the bean at key phase transitions.

All of our backing beans subclass InitializableBean, which uses this interface to do two things:

  • Initialize the bean (usually loading from the DB) before any other rendering is done.
  • Note when validation failed, in case that should influence the bean's data loading.

JSF Design Conventions

These new classes went along with a somewhat experimental set of guidelines for JSF design.

  • Every page has its own backing bean. Backing beans aren't shared between JSP pages. When we start to see code re-use, we refactor into a class hierarchy or helper classes, or write a JSF tag.
  • Each backing bean is Serializable. The serializable fields consist only of what JSF needs to manage the component model, including input fields and context variables. Everything needed to recreate the view can then be maintained across requests (courtesy of the gbx:flowState tag). It's sometimes a chore to figure out what should be "transient" and what shouldn't, but the payback is that it's much easier to fix jack-in-the-box bugs.
  • Confine DB queries which set up the page to the bean's "init()" method. In other words, data loading is not done in action handlers, in getters, etc. This ensures that we load our bean only after all the criteria we need is in place, and prevents the "retrieve it and then throw it away" redundancies and "where did that object go to?" surprises of the Pilot.
  • Avoid unnecessary DB queries. That sounds like a no-brainer, but it was hard to avoid in the Pilot. In particular, we can selectively avoid refreshing from the DB for work-in-progress request-threads (such as the "what if?" support of the Feedback Options page) or when dealing with a validation error.
  • Keep domain classes out of tag references and the serialized backing bean. That is, complex persisted objects such as "Assignment" or "GradeRecord" don't show up in input fields, or in "rendered" or "disabled" attributes, table definitions, etc. Instead, we explicitly copy any fields we need to and from the business object. IDs rather than whole objects are used to coordinate with the DB.

Most of these turned out to be worthwhile. For our next release, though, we're dropping that last guideline. It requires more initial code and more maintenance than directly referring to the domain objects, and it forces the backing bean to more closely track changes to the page and business logic. The benefits were: 1) The bean explicitly controls what's on the page. 2) The domain classes don't have to be serializable. (If we serialized the Assignment Grade Record as currently defined, it would pull in its referred-to Gradable Object, which in turn would pull in its referred-to Gradebook, all bloating the request traffic.) The first was what I was most interested in, since our goal was predictability. The second turned out to be bogus, since we're now far more cautious about direct links and collections in Hibernate-mapped classes than we were in the Pilot.

We're in the process of changing this, mostly to bring optimistic locking back, and the resulting backing bean logic is far simpler than what we delivered in the Sakai 2.0 release.

Incremental Generalization with JSF

JSP 2 makes it easy to do lazy generalization of JSP fragments.

JSP 2

  1. Embedded
  2. Included
  3. JSP tag
  4. Java tag

Start off with a chunk of JSP/HTML. If you're about to copy and paste it, break it out into an include file. If there's something dynamic about it, you can very quickly create a new tag in JSP itself. Then if it turns out you need more efficiency or flexibility, do the extra work of writing a tag in Java.

JSF 1.1

  1. Embedded
  2. Java+ component

With JSF 1.1, you start off with a chunk of JSP/HTML, and then – you're pretty much stuck. The next step you can take is writing a new JSF component, and that's more labor intensive and brittle than writing a JSP tag. That's probably why sample JSF applications tend to be loaded with copied-and-pasted JSP.

The solution turned out to be a poorly documented MyFaces component, "aliasBean".

Here's an example of how it works. The Gradebook has a page to add a new assignment, specifying all its properties, and a page to edit an assignment's properties. Naturally, these pages have a lot in common and it would be good to guard against creeping discrepencies between them. But the need is a little too specialized to make defining a new JSF tag seem worthwhile. Enter x:aliasBean.

From addAssignment.jsp :

<x:aliasBean alias="#{bean}" value="#{addAssignmentBean}">
<%@include file="/inc/assignmentEditing.jspf"%>
</x:aliasBean>

From editAssignment.jsp :

<x:aliasBean alias="#{bean}" value="#{editAssignmentBean}">
<%@include file="/inc/assignmentEditing.jspf"%>
</x:aliasBean>

From inc/assignmentEditing.jspf :

<h:inputText id="title"
  value="#{bean.title}"
  required="true"
  onkeypress="return submitOnEnter(event, 'gbForm:saveButton');">

Improved JSF Messaging

Integrated handling of notification and warning messages is one of JSF's more enticing and frustrating promises. The promise fails in two ways. First, formatting options for the messages are very limited in the JSF specification – and even more limited in Sun's implementation! Second, messages aren't carried across redirects. As a result, you can't queue up a success message to be displayed after JSF navigation returns the user to a bookmarkable URL.

For better formatting, we replaced the default message renderer with DivMessagesRenderer. This puts each message in its own HTML "<div>", allowing for more flexible layouts.

For carrying messages across redirects, we used a session-scoped Faces-managed bean, MessagingBean.

Both of these approaches would need polishing before being suitable for general use. But they serve as a stopgap for us until something better comes along.

Conclusions and Futures

With all this, plus a slightly dumbed-down UI to make up for the lack of components, we were able to produce an adequate, stable, scalable, and usable UI (including support of the back button and multiple window/tab views) on schedule.

We're hoping to find more useful externally created components in the future, and looking foward to the eventual JSF 1.2. Before then, we may tackle some of these remaining problems:

  • "Track-switching" URLs - By this, I mean an entry point which exists to redirect the user elsewhere based on their context. Because redirects can't happen once the JSF rendering phase begins, we're using a servlet to handle entry URLs. There may be a way to manage this within JSF with a tool-specific PhaseListener, however.
  • Eliminating misleading URLs - In JSF 1.1, the real URL corresponding to the current page is always one step behind what the browser reports as the current URL unless the application performs a redirect between pages, which doubles the number of HTTP requests and loses any request-scoped data. It would be nice to find a way around this irritation.
  • Precompile JSPs - With complex pages, the first visitor to a page when the application is installed is likely to experience a noticeable delay while the JSP is compiled. We're hoping to start precompilings, both for that reason and to catch bad JSP during builds.

One of the rather heterodox things that we did in Samigo--not 100% but we tended to do this more than most applications:

We separated out the controller and business logic out from the backing beans. We did this by encapsulating the logic in ActionListeners. We had a lot of religious arguments about this. (Reading Samigo code you can sense the preferences of different developers! )

There were three thoughts on this:
1. We were porting a lot of code that had already been developed in Struts, which had a clear divison betwen Actions and ActionForms.
2. We could decouple the UI directed navigation (action attributes) and processing, our beans were pretty clean, relatively logicless Serializable property sheets.
3. We didn't fall into the normal way of doing things, maybe because we didn't know any better, or to put it another way, most of the books were nto out yet.

Another technique we used is to put strictly UI-logic for formatting into computed backing bean properties. There's still a lot of gnarly conditional logic in the JSF pages, but this got us out of a lot of sticky situations. So I recommend that if you have, say, a complicated rendered attribute expression, you put the tests in a computed readonly boolean property. This will clean up your page.

Putting conditional logic into readonly backing bean properties seemed like good practice to us, too. Besides cleaning up the JSP, it helps in debugging.

For lack of any other clear direction, we used ActionListener to handle any request that would keep the user on the same page and reserved Action for requests that might send the user elsewhere. That way the interface choice itself would signal the intent to stay within the same URL and the same backing bean, and we could also avoid one class of mapping bugs.

Following up on the "request thread" topic, anyone who's considering development with JSF should look into Spring Web Flow. Among its data-scoping choices are flow scope (for an entire workflow instance) and flash scope (which preserves state across a redirect or a refresh without the issues of session scope). Flash scope might even let JSF applications provide legitimate URLs!

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.5 Build:#811 Jul 25, 2007) - Bug/feature request - Contact Administrators