17.2. Improving the application with Seam

The web application you've written to search and edit web items can be improved if you add Seam into the mix. You start with basic Seam features:

  • Seam makes the JSF backing bean unnecessary. You can bind JSF widget values and actions directly to EJB stateful and stateless session beans. Seam introduces a unified component model: All your classes can be turned into Seam components with annotations. Components are wired together in a loosely coupled fashion, with string expressions.

  • Seam introduces new contexts and manages component scope automatically. This rich context model includes logical contexts that are meaningful to the application, such as a conversation or business-process context.

  • Seam introduces a stateful programming model, which is great for conversations. A stateful application with Seam-managed conversations works in multiple browser windows with no extra effort.

This is a short list of what Seam can do; there is much more that you'll put to use later. Let's first create a basic conversational, stateful, simple Seam application. Your first step is Seam setup and configuration.

If you want to follow the examples with code, download the CaveatEmptor package for Seam from http://caveatemptor.hibernate.org, and open it in your IDE. This is also a good starting point if you want to code your own Seam project later.

17.2.1. Configuring Seam

Figure 17.3 shows the files before and after the changes you make to the web application in the following sections.

Figure 17-3. The application archive before and after Seam was introduced

Two major changes are made: The JSF backing bean is no longer necessary, and the beans.jar archive has a new file, seam.properties. This file contains two Seam configuration options for this simple application (listing 17.7).

Listing 17-7. A simple seam.properties configuration file
org.jboss.seam.core.init.jndiPattern = caveatEmptor/#{ejbName}/local
org.jboss.seam.core.manager.conversationTimeout = 600000

The first setting is necessary for Seam to integrate with an EJB 3.0 container. Because Seam is now responsible for wiring component instances at runtime, it needs to know how to obtain EJBs through lookup. The JNDI pattern shown here is for JBoss application server. (Seam runs on any Java EE 5.0 server and even with and without EJB 3.0 in regular Tomcat. We think it's most convenient if you start with JBoss application server, because you don't need to install any extra services.)

To completely integrate Seam with EJB 3.0, Seam also needs to intercept all calls to your EJBs. This is easy to do, thanks to EJB 3.0 support for custom interceptors. You won't see any interceptors in the code of your classes, because they're usually defined with a global wildcard that matches all EJBs in META-INF/ejb-jar.xml (not shown here). If you download a Seam example, it will have this file.

The second setting in seam.properties defines that Seam can destroy an inactive user conversation after 600,000 milliseconds (10 minutes). This setting frees up memory in the HTTP session when a user decides to go to lunch.

The seam.properties file is not only a configuration file for Seam—it's also a marker. When Seam starts up, it scans the classpath and all archives for Seam components (classes with the right annotation). However, scanning all JARs would be too expensive, so Seam only scans JAR files and directories recursively that have a seam.properties file in the root path. Even if you don't have any configuration settings, you need an empty seam.properties file in the archive with your Seam component classes.

You can find more Seam configuration options, and the integration with JSF and the servlet container, in web.xml and faces-config.xml. We'll get back to faces-config.xml later; web.xml isn't interesting (see the commented file in the CaveatEmptor package).

Seam can also be configured with a components.xml file in the WARs WEB-INF directory. You'll use that later when more complex configuration of components is required. (Much of Seam is written as Seam components. The string org.jboss.seam.core.manager is a component name, and conversationTimeout is a property you can access like any other component property.)

Your next step is replacing the JSF backing bean with a Seam component.

17.2.2. Binding pages to stateful Seam components

The search.xhtml page doesn't change at all; review the code in listing 17.2. This page has a value binding to itemEditor.itemId and an action binding to itemEditor.doSearch. When the page is rendered by the JSF servlet, these expressions are evaluated, and the widgets are bound to the respective methods in the itemEditor bean.

The EJB component interface

The itemEditor bean is now an EJB. The interface of this EJB is EditItem.java (listing 17.8).

Listing 17-8. The interface of a stateful component
package auction.beans;

import ...

public interface EditItem {

    // Value binding methods
    public Long getItemId();
    public void setItemId(Long itemId);

    public Item getItem();

    // Action binding methods
    public String doSearch();
    public String doSave();

    // Cleanup routine
    public void destroy();

}

The first two methods are the getter and setter for the value binding of the search input text field of the page. The getItem() method (you don't need a setter here) will be used later by the edit page. The doSearch() method is bound to the Search button, doSave() will be bound to a button on the edit page.

This is an interface for a stateful component. A stateful component is instantiated when it's first requested—for example, because a page is rendered for the first time. Every stateful component needs a method that the runtime environment can call when the component is destroyed. You could use the doSave() method and say that the component's lifecycle ends when this method completes, but you'll see in a minute why a separate method is cleaner.

Next, let's look at the implementation of this interface.

The EJB component implementation

The standard stateful component in EJB 3.0 is a stateful session bean. The implementation in EditItemBean.java is a POJO class with a few extra annotations. In listing 17.9, all Seam annotations are shown in bold.

Listing 17-9. The implementation of a stateful component
package auction.beans;

import ...

@Name("itemEditor")
							
@Scope(ScopeType.CONVERSATION)
@Stateful
public class EditItemBean implements EditItem { @PersistenceContext
EntityManager em; private Long itemId;
public Long getItemId() { return itemId; } public void setItemId(Long itemId) { this.itemId = itemId; } private Item item; public Item getItem() { return item; } @Begin
public String doSearch() { item = em.find(Item.class, itemId); if (item == null) FacesMessages.instance().add( "itemSearchField", new FacesMessage("No item found.") ); return item != null ? "found" : null; } @End
public String doSave() { item = em.merge(item); return "success"; } @Destroy
@Remove public void destroy() {} }

❶ The Seam @Name annotation declares the name for this Seam component. It also turns this EJB into a Seam component. You can now reference this component anywhere under this name.

❷ When an instance of this component is required, Seam instantiates it for you. Seam puts the instance into a context under its name. Here's a formal description: An instance of EditItem is managed by Seam in the conversation context, as a value of the contextual variable itemEditor.

❸ A POJO needs the EJB 3.0 @Stateful annotation to become a stateful session bean.

❹ The EJB 3.0 container injects an EntityManager with a fresh persistence context into this bean, before a method is called by any client of the bean. The persistence context is closed when the method returns (assuming this method call is also the scope of a system transaction, which is the default here).

❺ This stateful component holds state internally, in the fields itemId and item. The state is exposed with property accessor methods.

❻ A Seam @Begin annotation marks a method that begins a long-running conversation. If a JSF action triggers a call to this method, Seam maintains the state of this component across HTTP requests. The doSearch() method returns a string outcome (or null) and generates a JSF message that can be rendered on a page. The Seam FacesMessages helper makes this message-passing easy.

❼ A Seam @End annotation marks a method that ends a long-running conversation. If a JSF action triggers a call to this method, and the method returns, Seam will destroy the component's state and no longer maintain it across HTTP requests.

❽ The Seam @Destroy annotation marks the method that is called by Seam when the component state has to be destroyed (when the end of the conversation has been reached). This is useful for internal cleanup (there is nothing to do in this case). The EJB 3.0 @Remove annotation marks the method that a client (Seam, in this case) has to call to remove the stateful session bean instance. These two annotations usually appear on the same method.

Why don't you mark the doSave() method with @End, @Destroy, and @Remove? The doSave() method might throw an exception, and this exception has to roll back any system transaction. Seam, however, logs and swallows any exception thrown by its @Destroy method, so you frequently see empty destroy methods in stateful Seam components. Furthermore, the component instance is needed for a little while after the saving of an item, to render a response.

This EJB implementation encapsulates all application logic; there is no more Java code anywhere else (well, there is the Item entity class). If you ignore trivial code, the application logic is only four lines in the two action methods.

Two more changes are necessary to make the application work. Some value bindings in edit.xhtml need to be modified, and the block of XML that defined the old JSF backing bean can be removed from faces-config.xml.

Binding values and actions

Open edit.xhtml, and change the value bindings of the JSF input widgets as shown in listing 17.10.

Listing 17-10. The edit.xhtml page is bound to a Seam component.
...
<h2>Editing item: #{itemEditor.itemId}</h2>

<h:form>

    <span class="errors"><h:messages/></span>

    <div class="entry">
        <div class="label">Name:</div>
        <div class="input">
            <h:inputText required="true" size="25"
                         value="#{itemEditor.item.name}">
                <f:validateLength minimum="5" maximum="255"/>
            </h:inputText>
        </div>
    </div>
    <div class="entry">
        <div class="label">Description:</div>
        <div class="input">
            <h:inputTextarea cols="40" rows="4" required="true"
                             value="#{itemEditor.item.description}">
                <f:validateLength minimum="10" maximum="4000"/>
            </h:inputTextarea>
        </div>
    </div>

    <div class="entry">
        <div class="label">Initial price (USD):</div>
        <div class="input">
            <h:inputText size="6" required="true"
                         value="#{itemEditor.item.initialPrice}" >
                <f:converter converterId="javax.faces.BigDecimal"/>
            </h:inputText>
        </div>
    </div>

    <div class="entry">
        <div class="label">&#160;</div>
        <div class="input">
            <h:commandButton value="Save" styleClass="button"
                             action="#{itemEditor.doSave}"/>
        </div>
    </div>

</h:form>
...

The bindings that changed are the expressions for the name, description, and initial price input fields. They now reference itemEditor.item, which can be resolved to the Seam component's getItem() method. JSF calls getName() and setName() on the returned Item entity to synchronize the state of the widget. The same technique is used to bind and synchronize the description and initial price of the item. When the user enters a new price, the initialPrice of the Item instance that is held by the itemEditor component is automatically updated.

The action binding for the Save button doesn't change—the method doSave() of the itemEditor component is still the right listener. You can see how logical component names and the expression language allow you to couple the view and the business layer easily and not too tightly.

Finally, update faces-config.xml as shown in listing 17.11.

Listing 17-11. The JSF configuration file without backing beans
...

<faces-config>

    <navigation-rule>
        <from-view-id>/search.xhtml</from-view-id>
        <navigation-case>
            <from-outcome>found</from-outcome>
            <to-view-id>/edit.xhtml</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <from-view-id>/edit.xhtml</from-view-id>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/search.xhtml</to-view-id>
        </navigation-case>
    </navigation-rule>

    <!-- Integrate Seam with the JSF request processing model -->
    <lifecycle>
        <phase-listener>
            org.jboss.seam.jsf.SeamPhaseListener
        </phase-listener>
    </lifecycle>

</faces-config>

Compare this to the previous JSF configuration in listing 17.6. The backing bean declaration is gone (moved to two Seam annotations on the EJB). The phase listener is new: Seam has to hook into the JSF servlet and listen to the processing of every HTTP request. A custom JSF phase listener integrates Seam with JSF.

We presented quite a few new concepts, which you probably have never seen if this is your first contact with Seam. Let's analyze the application in more detail and find out whether the issues we identified earlier for the plain JSF and EJB 3.0 application have been resolved.

17.2.3. Analyzing the Seam application

The interface of the web application hasn't changed; it looks the same. The only thing your users will probably notice is that they can search and edit items in several browser windows without overlapping state and data modifications.

Seam promotes a strong and well-defined stateful application programming model. Let's follow the flow of the application (figure 17.4) and find out how this works internally.

Figure 17-4. The request/response flow of the application

Opening the search page

When you open a browser window and enter the /search.jsf URL, an HTTPGET request is sent to the JSF servlet. The JSF processing lifecycle begins (figure 17.5).

Nothing really interesting happens until the JSF servlet enters the Render Response phase of the request processing. There is no view to restore and no HTTP request parameters that must be applied to the view components (the widgets).

When the response, the search.xhtml file, is rendered, JSF uses a variable resolver to evaluate the #{itemEditor.itemId} value binding. The Seam variable resolver is smarter than the standard JSF variable resolver. It searches for itemEditor not in the HTTP request, the HTTP session, and the global application context, but in Seams logical contexts. You're right if you think these logical contexts are the same—we'll have much more to say about this in a moment. For now, think about Seam contexts as variable holders that are searched hierarchically, from the context with the narrowest scope (the current event) to the context with the widest scope (the current application).

Figure 17-5. Seam is active when JSF renders the response, the search page.

The variable itemEditor can't be found. So, Seam's component handler starts looking for a Seam component with that name. It finds the stateful session bean you've written and creates an instance. This EJB instance is then given to the JSF page renderer, and the renderer puts the return value of getItemId() into the search input text field. The method returns null, so the field is empty when you open the page for the first time.

The Seam component handler also realizes that the stateful session bean had an @Scope(CONVERSATION) annotation. The instance is therefore put into the conversation context, as a value of the contextual variable itemEditor, the name of the component.

When the page is rendered completely, Seam is invoked again (through the Seam phase listener). The Seam event context has a small scope: It's able to hold variables only during a single HTTP request. Seam destroys this context and everything inside (nothing you currently need).

Seam also destroys the current conversation context and with it the itemEditor variable that was just created. This may surprise you—you probably expected the stateful session bean to be good for several requests. However, the scope of the conversation context is a single HTTP request, if nobody promotes it to a long-running conversation during that request. You promote a short single-request conversation to a long-running conversation by calling a component method that has been marked with @Begin. This didn't happen in this request.

The search page is now displayed by the browser, and the application waits for user input and a click of the Search button.

Searching for an item

When a user clicks the Search button, an HTTP POST request is send to the server and processed by JSF (figure 17.6). You have to look at the source code of search.xhtml and EditItemBean to understand this illustration.

Stored in the previous request (usually in the HTTP session on the server), JSF now finds a widget tree that represents the view (search.xhtml) and re-creates it internally. This widget tree is small: It has a form, an input text field and a submit button. In Apply Request Parameters, all user input is taken from the HTTP request and synchronized with the state of the widgets. The input text field widget now holds the search string entered by the user.


Tip:

Debugging the JSF widget tree—Facelets can show you the JSF widget tree. Put <ui:debug hotkey="D"/> anywhere in your page, and open the page in your browser (as a JSF URL, of course). Now press Ctrl+Shift+d, and a pop-up window with the JSF widget/component tree opens. If you click Scoped Variables, you can see where Seam internally stores its contexts and managers (this probably isn't very interesting if you are not a Seam developer).


During Process Validations, the JSF validator ensures that the search string entered by the user is a nonnegative integer and that the input value is present. If validation fails, the JSF servlet jumps to the Render Response phase and renders the search.xhtml page again with error messages (the processing of this phase looks like in figure 17.6).

After validation, JSF synchronizes the values of the model objects that have been bound to widgets. It calls itemEditor.setItemId(). This variable is resolved by Seam, with a lookup in all Seam contexts. Because no itemEditor variable is found in any context, a new instance of EditItemBean is created and placed into the conversation context. The setItemId() method is called on this stateful session bean instance.

Figure 17-6. Seam participates in the processing of the search action.

JSF now executes the action of the request by calling the bound method itemEditor.doSearch. Seam resolves the itemEditor variable and finds it in the conversation context. The doSearch() method is called on the EditItemBean instance, and the EJB 3.0 container handles transaction and persistence context during that call. Two things happen during the call: The item member variable of the itemEditor now holds an Item instance found in the database (or null, if nothing was found), and the @Begin annotation promotes the current conversation to a long-running conversation. The conversation context is held by Seam until a method with an @End annotation is called.

The doSearch() method returns the string found, or null. This outcome is evaluated by JSF, and the navigation rules from faces-config.xml apply. If the outcome is null, the search.xhtml page is rendered with the Item not found error message. If the outcome is found, the navigation rules declare that the edit.xhtml page is rendered.

During rendering of the edit.xhtml page, the variable itemEditor must be resolved again by JSF. Seam finds the itemEditor context variable in the conversation context, and JSF binds values of widgets on the page (text output, text input) to the properties of the item instance returned by itemEditor.getItem().


Tip:

Browsing the Seam contexts—You can debug a Seam application more easily if you use the Seam debugging screen. This screen must be enabled. To do so, edit your seam.properties file and add org.jboss. seam.core.init.debug = true. Now, access the URL /debug.jsf to browse the Seam contexts for this browser window. You can see all the variables and the values that are in the current conversation, session, process, and application contexts.


At the end of the request, Seam destroys its event context. The conversation context isn't destroyed; the user of the application started a long-running conversation by executing a search. The application waits for user input while showing the edit page. If the user searches again in another browser window, a second, concurrently running conversation is started and promoted to a long-running conversation. The two conversations and their contexts are isolated automatically by Seam.

Editing an item

When the user clicks Save, the edit form is submitted to the server with an HTTP POST request (figure 17.7).

The view that is restored in this request is edit.xhtml, JSF recreates an internal widget tree of the form and all its fields and applies the HTTP request values. Validation is slightly more complex; you've defined a few more JSF validators on the edit.xhtml page.

After successful validation, JSF updates the bound model values by calling the setter methods on the Item instance returned by itemEditor.getItem(). The itemEditor binding resolves (through Seam) to a contextual variable in the current conversation context. Seam extended the conversation context into the current request, because it was promoted to a long-running conversation in the previous request.

Figure 17-7. Seam participates in the processing of the edit action.

Next, itemEditor.doSave() is called; the variable is again resolved in the conversation context. The code in EditItemBean either throws an exception (if the EJB 3.0 container or the EntityManager throw an exception) or returns the string outcome success. The method is marked as @End, so the Seam manager marks the current conversation for cleanup after the Render Response phase.

The string outcome success is mapped to /search.xhtml in the JSF navigation rules. During Render Response, the value bindings on the search.xhtml page must be resolved. The only value binding is #{itemEditor.itemId}, so Seam again tries to find the itemEditor component in all contexts. The itemEditor from the (demoted but still active) conversation context is used, and getItemId() returns a value. The user therefore sees the input field not empty, but showing the same search value that was entered at the beginning of the conversation.

When Render Response completes, Seam removes the demoted conversation context and destroys all stateful components instances that live in that context. The destroy() method is called on the EditItemBean. Because it's marked with @Remove, the EJB 3.0 container also cleans up the stateful session bean internally. The user now sees the search page and can begin another conversation.

If you've never used JSF, this is a lot of new information to digest. On the other hand, if you're familiar with JSF, you can see that Seam is basically listening to the processing phases of the JSF servlet and replacing the variable resolver for value and action bindings with a more powerful variation.

We've barely scratched the surface of Seam with this trivial application. Let's discuss some more interesting and advanced features of Seam that make creating complex web applications with a database back end just as easy.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset