Chapter 13. How Do I ...

EJB Integration Seam

One of the things that makes developing web applications in Java harder than it should be is a mismatch between user interface and persistence frameworks. The two sides of the enterprise Java coin exist and mature independently, without much collaboration or synergy.

Traditionally, implementing Java-based web applications meant learning two frameworks one for the user interface (UI) and another for the backend. For example: JSF and Hibernate; Tapestry and EJB3; Webwork and IBATIS, etc. Then you must learn to use the two frameworks together. Synergy between the two could make development much easier, if only UI and persistence frameworks could somehow be united.

Enter Seam, from JBoss. Seam is a new approach to web development that unites JSF and EJB3 (or Hibernate) into a single potent framework with compelling productivity gains over traditional Java-based web frameworks. Seam works with either Hibernate or EJB3, and you can run it either in the JBoss server or Tomcat 5.5. Next, we see how it works.

An Address Book

To illustrate Seam fundamentals, let's explore the implementation of a Seam address book application, which maintains a list of contacts in a database. The address book is a typical create-read-update-delete application. Figure 12-10 shows how to add contacts to the database. Figure 12-11 on page 599 and Figure 12-12 on page 600 show how to delete and edit contacts, respectively.

Figure 12-10. Adding two contacts to an empty address book


Figure 12-11. Deleting a contact in the address book


Figure 12-12. Editing a contact in the address book


The address book has three JSP pages: the address book page, which lists all the contacts in the address book, a page to add a contact, and a page to edit a contact. From looking at those JSP pages, you cannot discern that this is a Seam application.

For example, here is an excerpt from addressBook.jsp, as shown in Figure 12-10:

   <%@ page contentType="text/html;charset=UTF-8" language="java" %>    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>    <f:view>       <html>          <head>             <title>Users</title>             <link href="styles.css" type="text/css" rel="stylesheet"/>          </head>          <body>             <div >                <h:outputText value="Address Book"                style/>             </div>             <h:form>                <h:dataTable value="#{contacts}"                      var="currentContact"                      style                      rowClasses="contactsEvenRow, contactsOddRow">                    <h:column>                       <h:commandButton value="delete"                         action="#{addressBook.delete}"/>                    </h:column>                    <h:column>                      <f:facet name="header">                          <h:outputText value="Name"/>                      </f:facet>                       <h:commandLink value="#{currentContact.name}"                         action="#{addressBook.beginEdit}"/>                    </h:column>                   <%-- The rest of the columns in the table have been ommitted to                        save space.                   --%>                </h:dataTable>                <h:commandLink value="Create more contacts..." action="addContact"/>             </h:form>           </body>       </html>    </f:view>     

As you might expect, the contacts table is implemented with an h:dataTable tag. The value attribute for that tag points to a managed bean named contacts, and in the body of the h:dataTable tag we access another managed bean named address-Book. We use this bean to wire buttons and links to JSF action methods.

The application's other two JSP pages, addContact.jsp and editContact.jsp, are equally innocuous, plain-vanilla JSF views without the slightest hint of any framework other than JSF.

The interesting part of this application lies in its managed beans. You would never know it from the JSP page alone, but the contacts and addressBook beans from the preceding code are both EJBs. The former is an entity bean, whereas the latter is a stateful session bean. Now we see how it all fits together.

Configuration

Our application's configuration consists of two XML files: one for JSF and another for persistence. As is typical for Seam applications, our JSF configuration file contains no managed bean declarations those have been transformed into annotations. In fact, our JSF configuration file consists almost entirely of navigation rules for navigating from one web page to another. The JSF configuration file is so unremarkable that it deserves no further mention.

The persistence XML file is marginally more interesting:

   <persistence>       <persistence-unit name="userDatabase">          <provider>org.hibernate.ejb.HibernatePersistence</provider>          <jta-data-source>java:/DefaultDS</jta-data-source>          <properties>             <property name="hibernate.dialect"                value="org.hibernate.dialect.HSQLDialect"/>             <property name="hibernate.hbm2ddl.auto" value="create-drop"/>          </properties>       </persistence-unit>    </persistence>

In the preceding XML file, we declare our database intentions, including the database name, data source, and Hibernate SQL dialect.

That is essentially all there is for configuration. For our implementation, we used the JBoss embedded EJB server with Tomcat 5.5.

To understand the address book implementation, we start on the ground floor: the database.

Entity Beans

We are storing contacts in a database, so we need an entity bean:

   @Entity    @Name("contact")    @Scope(ScopeType.EVENT)    @Table(name="contacts")    public class Contact implements Serializable {       private static final long serialVersionUID = 48L;       private String name, streetAddress, city;       private String state, country;       ...       public Contact(String name) {          this.name = name;        }       public Contact() {}       @Id @NotNull @Length(min=5, max=25)       public String getName() {          return name;       }       public void setName(String name) {          this.name = name;       }      // Standard JavaBeans setters and getters for the      // remaining variables are ommitted.       public String toString() {          return "Contact(" + name + ")";       }    }     

The annotations before the class declaration state that the Contact class represents an entity bean. We access that bean, named contact, in addContact.jsp:

   <h:panelGrid columns="2">       <h:outputText value="Name"/>       <h:panelGroup>          <h:inputText              value="#{contact.name}"             size="20"/>          <h:message for="name"/>       </h:panelGroup>       <h:outputText value="Street Address"/>       <h:panelGroup>          <h:inputText              value="#{contact.streetAddress}"             size="25"/>          <h:message for="streetAddress"/>        </h:panelGroup>       ...       <h:outputText value=""/>       <h:commandButton value="Add contact"           action="#{addressBook.addToBook}"/>       <h:commandButton value="Cancel"          action="#{addressBook.cancel}"/>    </h:panelGrid>     

By virtue of the annotations in the Contact class, when Seam first encounters the name contact in a value expression, it creates an instance of com.corejsf.Contact and places it in request scope (Seam refers to request scope as event scope). We also specify the name of the database table contacts that corresponds to our entity bean.

The @Id annotation designates the name property as the primary key for the contacts table. The @NotNull and @Length annotations specify that the name property cannot be null and must contain between five and 25 characters.

Note

In the preceding code fragment, we used a total of seven annotations to add persistence to a plain old Java object (POJO) and to transform it into a JSF managed bean. In fact, we used three distinct types of annotations:

  • EJB3

  • JSF

  • Hibernate validator framework

The @Entity, @Table, and @Id annotations are EJB3 annotations, whereas the @Name and @Scope annotations are JSF-related. Finally, the @NotNull and @Length annotations are for the Hibernate validator framework, which can be used with either Hibernate or EJB3.


Note

Seam performs validation at the model level, not the view level, as is typical for JSF applications.


Stateful Session Beans

Now we have a relatively simple-minded entity bean that we can persist to the database, so it is time to look at the class where the real action is: the stateful session bean that harbors JSF action methods called from JSP pages. First, we declare a local interface:

   package com.corejsf;    import javax.ejb.Local;    @Local    public interface AddressBook {       public String addToBook();       public String delete();       public String beginEdit();       public String edit();       public void findContacts();    }

Next, we implement the stateful session bean:

   // NOTE: this is not a complete listing. Parts of this class are purposely ommitted    // pending further discussion.    @Stateful    @Scope (ScopeType.SESSION)    @Name("addressBook")    public class AddressBookAction implements Serializable, AddressBook {       @In(required=false) private Contact contact;       ...       @PersistenceContext(type= PersistenceContextType.EXTENDED)       private EntityManager em;       ...       @IfInvalid(outcome=Outcome.REDISPLAY)       public String addToBook() {          List existing = em.createQuery("select name from Contact where name=:name")             .setParameter("name", contact.getName())             .getResultList();          if (existing.size()==0) {             // save to the database if the contact doesn't             // already exist             em.persist(contact);              ...             return "success";          }          else {             facesContext.addMessage(null,                new FacesMessage("contact already exists"));             return null;          }       }       ...       @Remove @Destroy       public void destroy() {}    }     

The @Name annotation specifies the name of a managed bean. We referenced that addressBook bean from addressBook.jsp, listed on page 598. We use the @Scope annotation to specify the addressBook bean's scope.

With the @In annotation, we inject the contact instance, which means that Seam will intercept all AddressBookAction method calls, and if a scoped variable named contact exists, Seam will inject it into AddressBookAction's contact property before invoking the method. For the contact property, injection is not required; otherwise, Seam would throw an exception for any method called when there was no contact scoped variable for Seam to inject.

In the addToBook method, we use the EJB entity manager to save the contact to the database. The addToBook method is called from addContact.jsp:

            <h:form>                <h:panelGrid columns="2">                   <h:outputText value="Name"/>                   <h:panelGroup>                      <h:inputText                          value="#{contact.name}"                         size="20"/>                      <h:message for="name"/>                   </h:panelGroup>                   ...                   <h:outputText value=""/>                   <h:commandButton value="Add contact"                      action="#{addressBook.addToBook}"/>                </h:panelGrid>              </h:form>          </body>       </html>    </f:view>

Here is how the scenario unfolds: When you load addContact.jsp, Seam encounters the expression #{contact.name}. Since the contact bean is request-scoped (or event-scoped in Seam-speak), Seam creates an instance of com.corejsf.Contact and stores it in request scope under the name contact. Then Seam calls contact.get-Name() to populate the name text field as the page loads. Subsequently, the contact bean is available throughout the rest of the page.

When the user submits the form, assuming all submitted values pass validation, Seam invokes the corresonding setter methods for the contact object's properties and invokes addressBook.addToBook().

When Seam intercepts the call to addressBook.addToBook(), it first injects the value of the request-scoped contact variable into the addressBook's contact property; thus, addToBook() has access to the contact entity bean, and from there it uses the EJB entity manager to drive the changes home to the database.

Note

The address book has two EJBs: an entity bean representing a contact and a stateful session bean. The contact entity beans are stored in the database, whereas the stateful session bean contains JSF action methods and maintains the list of contacts in the database. The stateful session bean could just as easily have been implemented as a JavaBean. But we wanted the convenience of database access in our JSF actions, so we opted for a session bean, as is often the case for Seam applications.


JSF DataModel Integration

Seam has built-in support for JSF tables. Once again, take a look at a severely truncated listing of the contacts table in addressBook.jsp:

   <h:dataTable value="#{contacts}"       var="currentContact"       style       rowClasses="contactsEvenRow, contactsOddRow">       ...    </h:dataTable>

Now we revisit the stateful session bean, AddressBookAction, that we discussed in "Stateful Session Beans" on page 603. In that discussion, we ommitted some details, which we explore in the next couple of sections. Here, we look at the @DataModel and @DataModelSelection annotations and their corresponding properties. First, we discuss @DataModel:

   public class AddressBookAction implements Serializable, AddressBook {       @DataModel       @Out(required=false)       private List<Contact> contacts;       ...       @Factory("contacts")       public void findContacts() {          contacts = em.createQuery("from Contact")             .getResultList();       }       ...    }

When Seam comes across <h:dataTable value="#{contacts}"...>...</h:dataTable> in addressBook.jsp, it looks for a scoped variable named contacts. If the contacts variable does not exist, Seam creates it with a call to the variable's factory method: AddressBookAction.findContacts(). That method performs a database query to ensare all the contacts in the database and stores the resulting list in the contacts variable. At the end of the factory method call, Seam exports the contacts variable to page scope, at the behest of the @Out annotation.

As you can see from this example, Seam factory methods let you wire a JSF component to a persistent object; in the preceding example, we wired a list of contacts from the database to a JSF table.

Seam also has special support for handling table selections. In AddressBookAction, we add a @DataModelSelection annotation:

   public class AddressBookAction implements Serializable, AddressBook {       @DataModel       @Out(required=false)       private List<Contact> contacts;       @DataModelSelection       @Out(required=false, scope=ScopeType.CONVERSATION)       private Contact selectedContact;       ...       @End       public String edit() {           em.persist(selectedContact);           contacts = em.createQuery("from Contact").getResultList();           return "edited";       }       ...       // This method is called from addressBook.jsp.       public String delete() {           // Deletes the selected contact from the database           contacts.remove(selectedContact);           em.remove(selectedContact);           return "deleted";       }       ...    }     

When the user clicks a button or link from the contacts table, Seam injects the selected contact into the AddressBookAction's selectedContact variable before entering the action method associated with the button or link. For example, when the user clicks a "delete" button on the address book page, Seam invokes AddressBookAction.delete(). But before it does, it injects the selected contact into the AddressBookAction's selectedContact variable. Interestingly, setter and getter methods are not required for the selectedContact variable it is enough to declare the variable and its annotation, and Seam takes care of the rest.

In addition to injecting the selectedContact variable, we also export (or outject, if you must) it to conversation scope, so we can access it in editContact.jsp. Next, we see what conversation scope is all about.

Conversation Scope

In web applications, we have request scope, which spans a single HTTP request, and session scope, which sticks around indefinitely. Often, when implementing a series of interactions, such as a wizard, for example, it would be nice to have a scope in between request and session. In Seam, that's conversation scope.

When we delete a contact from the address book, it is a one-step process. The user clicks a "delete" button, and Seam invokes AddressBookAction.delete(), as outlined in the previous section. Seam takes care to inject the selected contact before making the call (this is discussed in "JSF DataModel Integration" on page 606). The delete method deletes the contact from the database and updates the list of contacts.

However, editing a contact is a two-step process. It starts when the user clicks the link representing the contact's name:

   <h:dataTable value="#{contacts}" var="currentContact"          style          rowClasses="contactsEvenRow, contactsOddRow">       <h:column>          <h:commandButton value="delete"             action="#{addressBook.delete}"/>       </h:column>       <h:column>          <f:facet name="header">             <h:outputText value="Name"/>          </f:facet>          <h:commandLink value="#{currentContact.name}"             action="#{addressBook.beginEdit}"/>       </h:column>    </h:dataTable>

Seam invokes AddressBookAction.beginEdit(), once again injecting the selected contact into the selectedContact variable before making the call. Here is how beginEdit() is implemented:

   public class AddressBookAction implements Serializable, AddressBook {       ...       @DataModelSelection       @In(required=false)       @Out(required=false, scope=ScopeType.CONVERSATION)       private Contact selectedContact;       ...       @Begin public String beginEdit() {           return "edit";       }       @End public String edit() {            em.persist(selectedContact);            contacts = em.createQuery("from Contact").getResultList();            return "edited";       }    }

The beginEdit method is a JSF action method that returns a string outcome used by JSF to navigate to the next view. That is unremarkable. What is remarkable is the @Begin annotation, which signifies that beginEdit() starts a conversation. When we leave beginEdit(), Seam exports the selectedContact to conversation scope, where we subsequently access it in editContact.jsp.

The @End annotation, attached to the edit method, signifies the end of the conversation. When we exit that method, Seam removes the selectedContact from conversation scope.

Note

We could have eschewed conversations and instead stored the selected contact in session scope. In the edit method, we could have manually removed the selected contact from session scope, thereby creating a psuedo-conversation scope. In fact, many developers have done just that sort of thing; however, it is tedious and error prone. It is much more convenient to let the framework take care of that bookeeping so you can concentrate on higher-level concerns.




Core JavaServerT Faces
Core JavaServer(TM) Faces (2nd Edition)
ISBN: 0131738860
EAN: 2147483647
Year: 2004
Pages: 84

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net