Prev |  | Next

## Chapter 13. The Seam Application Framework

Seam makes it really easy to create applications by writing plain Java classes with annotations, which don't need to extend any special interfaces or superclasses. But we can simplify some common programming tasks even further, by providing a set of pre-built components which can be re-used either by configuration in `components.xml` (for very simple cases) or extension.

The Seam Application Framework can reduce the amount of code you need to write when doing basic database access in a web application, using either Hibernate or JPA.

We should emphasize that the framework is extremely simple, just a handful of simple classes that are easy to understand and extend. The "magic" is in Seam itself—the same magic you use when creating any Seam application even without using this framework.

## 13.1. Introduction

The components provided by the Seam application framework may be used in one of two different approaches. The first way is to install and configure an instance of the component in `components.xml`, just like we have done with other kinds of built-in Seam components. For example, the following fragment from `components.xml` installs a component which can perform basic CRUD operations for a `Person` entity:

    <framework:entity-home name="personHome"

                           entity-class="eg.Person"

                           entity-manager="#{personDatabase}">

        <framework:id>#{param.personId}</framework:id>

    </framework:entity-home>

If that looks a bit too much like "programming in XML" for your taste, you can use extension instead:

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> {

       @In EntityManager personDatabase;

       public EntityManager getEntityManager() {

          return personDatabase;

       }

    }

The second approach has one huge advantage: you can easily add extra functionality, and override the built-in functionality (the framework classes were carefully designed for extension and customization).

A second advantage is that your classes may be EJB stateful session beans, if you like. (They do not have to be, they can be plain JavaBean components if you prefer.) If you are using JBoss AS, you'll need 4.2.2.GA or later:

    @Stateful

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> implements LocalPersonHome {

    }

You can also make your classes stateless session beans. In this case you must use injection to provide the persistence context, even if it is called `entityManager`:

    @Stateless

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> implements LocalPersonHome {

       @In EntityManager entityManager;

       public EntityManager getPersistenceContext() {

          entityManager;

       }

    }

At this time, the Seam Application Framework provides four main built-in components: `EntityHome` and `HibernateEntityHome` for CRUD, along with `EntityQuery` and `HibernateEntityQuery` for queries.

The Home and Query components are written so that they can function with a scope of session, event or conversation. Which scope you use depends upon the state model you wish to use in your application.

The Seam Application Framework only works with Seam-managed persistence contexts. By default, the components will look for a persistence context named `entityManager`.

## 13.2. Home objects

A Home object provides persistence operations for a particular entity class. Suppose we have our trusty `Person` class:

    @Entity

    public class Person {

        @Id private Long id;

        private String firstName;

        private String lastName;

        private Country nationality;

        //getters and setters...

    }

We can define a `personHome` component either via configuration:

    <framework:entity-home name="personHome" entity-class="eg.Person" />

Or via extension:

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> {}

A Home object provides the following operations: `persist()`, `remove()`, `update()` and `getInstance()`. Before you can call the `remove()`, or `update()` operations, you must first set the identifier of the object you are interested in, using the `setId()` method.

We can use a Home directly from a JSF page, for example:

    <h1>Create Person</h1>

    <h:form>

        <div>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>

        <div>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>

        <div>

            <h:commandButton value="Create Person" action="#{personHome.persist}"/>

        </div>

    </h:form>

Usually, it is much nicer to be able to refer to the `Person` merely as `person`, so let's make that possible by adding a line to `components.xml`:

    <factory name="person"

             value="#{personHome.instance}"/>

    <framework:entity-home name="personHome"

                           entity-class="eg.Person" />

(If we are using configuration.) Or by adding a `@Factory` method to `PersonHome`:

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> {

        @Factory("person")

        public Person initPerson() { return getInstance(); }

    }

(If we are using extension.) This change simplifies our JSF page to the following:

    <h1>Create Person</h1>

    <h:form>

        <div>First name: <h:inputText value="#{person.firstName}"/></div>

        <div>Last name: <h:inputText value="#{person.lastName}"/></div>

        <div>

            <h:commandButton value="Create Person" action="#{personHome.persist}"/>

        </div>

    </h:form>

Well, that lets us create new `Person` entries. Yes, that is all the code that is required! Now, if we want to be able to display, update and delete pre-existing `Person` entries in the database, we need to be able to pass the entry identifier to the `PersonHome`. Page parameters are a great way to do that:

    <pages>

        <page view-id="/editPerson.jsp">

            <param name="personId" value="#{personHome.id}"/>

        </page>

    </pages>

Now we can add the extra operations to our JSF page:

    <h1>

        <h:outputText rendered="#{!personHome.managed}" value="Create Person"/>

        <h:outputText rendered="#{personHome.managed}" value="Edit Person"/>

    </h1>

    <h:form>

        <div>First name: <h:inputText value="#{person.firstName}"/></div>

        <div>Last name: <h:inputText value="#{person.lastName}"/></div>

        <div>

            <h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>

            <h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>

            <h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>

        </div>

    </h:form>

When we link to the page with no request parameters, the page will be displayed as a "Create Person" page. When we provide a value for the `personId` request parameter, it will be an "Edit Person" page.

Suppose we need to create `Person` entries with their nationality initialized. We can do that easily, via configuration:

    <factory name="person"

             value="#{personHome.instance}"/>

    <framework:entity-home name="personHome"

                           entity-class="eg.Person"

                           new-instance="#{newPerson}"/>

    <component name="newPerson"

               class="eg.Person">

        <property name="nationality">#{country}</property>

    </component>

Or by extension:

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> {

        @In Country country;

        @Factory("person")

        public Person initPerson() { return getInstance(); }

        protected Person createInstance() {

            return new Person(country);

        }

    }

Of course, the `Country` could be an object managed by another Home object, for example, `CountryHome`.

To add more sophisticated operations (association management, etc), we can just add methods to `PersonHome`.

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> {

        @In Country country;

        @Factory("person")

        public Person initPerson() { return getInstance(); }

        protected Person createInstance() {

            return new Person(country);

        }

        public void migrate()

        {

            getInstance().setCountry(country);

            update();

        }

    }

The Home object raises an `org.jboss.seam.afterTransactionSuccess` event when a transaction succeeds (a call to `persist()`, `update()` or `remove()` succeeds). By observing this event we can refresh our queries when the underlying entities are changed. If we only want to refresh certain queries when a particular entity is persited, updated or removed we can observe the `org.jboss.seam.afterTransactionSuccess.<name>` event (where `<name>` is the name of the entity).

The Home object automatically displays faces messages when an operation is successful. To customize these messages we can, again, use configuration:

    <factory name="person"

             value="#{personHome.instance}"/>

    <framework:entity-home name="personHome"

                           entity-class="eg.Person"

                           new-instance="#{newPerson}">

        <framework:created-message>New person #{person.firstName} #{person.lastName} created</framework:created-message>

        <framework:deleted-message>Person #{person.firstName} #{person.lastName} deleted</framework:deleted-message>

        <framework:updated-message>Person #{person.firstName} #{person.lastName} updated</framework:updated-message>

    </framework:entity-home>

    <component name="newPerson"

               class="eg.Person">

        <property name="nationality">#{country}</property>

    </component>

Or extension:

    @Name("personHome")

    public class PersonHome extends EntityHome<Person> {

        @In Country country;

        @Factory("person")

        public Person initPerson() { return getInstance(); }

        protected Person createInstance() {

            return new Person(country);

        }

        protected String getCreatedMessage() { return "New person #{person.firstName} #{person.lastName} created"; }

        protected String getUpdatedMessage() { return "Person #{person.firstName} #{person.lastName} updated"; }

        protected String getDeletedMessage() { return "Person #{person.firstName} #{person.lastName} deleted"; }

    }

But the best way to specify the messages is to put them in a resource bundle known to Seam (the bundle named `messages`, by default).

    Person_created=New person #{person.firstName} #{person.lastName} created
    Person_deleted=Person #{person.firstName} #{person.lastName} deleted
    Person_updated=Person #{person.firstName} #{person.lastName} updated

This enables internationalization, and keeps your code and configuration clean of presentation concerns.

The final step is to add validation functionality to the page, using `<s:validateAll>` and `<s:decorate>`, but I'll leave that for you to figure out.

## 13.3. Query objects

If we need a list of all `Person` instance in the database, we can use a Query object. For example:

    <framework:entity-query name="people"

                            ejbql="select p from Person p"/>

We can use it from a JSF page:

    <h1>List of people</h1>

    <h:dataTable value="#{people.resultList}" var="person">

        <h:column>

            <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">

                <f:param name="personId" value="#{person.id}"/>

            </s:link>

        </h:column>

    </h:dataTable>

We probably need to support pagination:

    <framework:entity-query name="people"

                            ejbql="select p from Person p"

                            order="lastName"

                            max-results="20"/>

We'll use a page parameter to determine the page to display:

    <pages>

        <page view-id="/searchPerson.jsp">

            <param name="firstResult" value="#{people.firstResult}"/>

        </page>

    </pages>

The JSF code for a pagination control is a bit verbose, but manageable:

    <h1>Search for people</h1>

    <h:dataTable value="#{people.resultList}" var="person">

        <h:column>

            <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">

                <f:param name="personId" value="#{person.id}"/>

            </s:link>

        </h:column>

    </h:dataTable>

    <s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">

        <f:param name="firstResult" value="0"/>

    </s:link>

    <s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">

        <f:param name="firstResult" value="#{people.previousFirstResult}"/>

    </s:link>

    <s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">

        <f:param name="firstResult" value="#{people.nextFirstResult}"/>

    </s:link>

    <s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">

        <f:param name="firstResult" value="#{people.lastFirstResult}"/>

    </s:link>

Real search screens let the user enter a bunch of optional search criteria to narrow the list of results returned. The Query object lets you specify optional "restrictions" to support this important usecase:

    <component name="examplePerson" class="Person"/>

    <framework:entity-query name="people"

                            ejbql="select p from Person p"

                            order="lastName"

                            max-results="20">

        <framework:restrictions>

            <value>lower(firstName) like lower( concat(#{examplePerson.firstName},'%') )</value>

            <value>lower(lastName) like lower( concat(#{examplePerson.lastName},'%') )</value>

        </framework:restrictions>

    </framework:entity-query>

Notice the use of an "example" object.

    <h1>Search for people</h1>

    <h:form>

        <div>First name: <h:inputText value="#{examplePerson.firstName}"/></div>

        <div>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>

        <div><h:commandButton value="Search" action="/search.jsp"/></div>

    </h:form>

    <h:dataTable value="#{people.resultList}" var="person">

        <h:column>

            <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">

                <f:param name="personId" value="#{person.id}"/>

            </s:link>

        </h:column>

    </h:dataTable>

To refresh the query when the underlying entities change we observe the `org.jboss.seam.afterTransactionSuccess` event:

    <event type="org.jboss.seam.afterTransactionSuccess">

        <action execute="#{people.refresh}" />

    </event>

Or, to just refresh the query when the person entity is persisted, updated or removed through `PersonHome`:

    <event type="org.jboss.seam.afterTransactionSuccess.Person">

        <action execute="#{people.refresh}" />

        </event>

Unfortunately Query objects don't work well with join fetch queries - the use of pagination with these queries is not recomended, and you'll have to implement your own method of calculating the total number of results (by overriding `getCountEjbql()`.

The examples in this section have all shown reuse by configuration. However, reuse by extension is equally possible for Query objects.

## 13.4. Controller objects

A totally optional part of the Seam Application Framework is the class `Controller` and its subclasses `EntityController` `HibernateEntityController` and `BusinessProcessController`. These classes provide nothing more than some convenience methods for access to commonly used built-in components and methods of built-in components. They help save a few keystrokes (characters can add up!) and provide a great launchpad for new users to explore the rich functionality built in to Seam.

For example, here is what `RegisterAction` from the Seam registration example would look like:

    @Stateless

    @Name("register")

    public class RegisterAction extends EntityController implements Register

    {

       @In private User user;

       public String register()

       {

          List existing = createQuery("select u.username from User u where u.username=:username")

             .setParameter("username", user.getUsername())

             .getResultList();

          if ( existing.size()==0 )

          {

             persist(user);

             info("Registered new user #{user.username}");

             return "/registered.jspx";

          }

          else

          {

             addFacesMessage("User #{user.username} already exists");

             return null;

          }

       }

    }

As you can see, its not an earthshattering improvement...

Prev |  | Front page | Next