SeamFramework.orgCommunity Documentation
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.
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
.
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 createValueExpression("New person #{person.firstName} #{person.lastName} created"); }
protected String getUpdatedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} updated"); }
protected String getDeletedMessage() { return createValueExpression("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.
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.
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...