Table of Contents
The Seam application is being developed using a clean eclipse and the latest JBoss Tools plugins. Getting started with the project is a breeze. We start by defining the database connection and the JBoss app server to use in our project wizard. We start the project wizard which asks questions about the project, any datasources, deployment options, and server runtimes and it creates everything you need to create a new project.
Since I chose the EAR deployment over the WAR deployment, we end up with a set of projects:
We have no jars to copy over, and no configuration files to set up, the project is ready to deploy and run. JBoss have done really well smoothing this piece out and letting users get up and running quickly.
In Seam, state management works a little differently. We don’t need to put every page into a work flow just to manage state. With Seam, state management is always present in the form of conversations. Conversations can either be default or long running. The default conversation lasts just for this page and into the next page. A long running conversation lasts until you explicitly end it. All pages run in a conversation whether it is the default temporary conversation or whether it is a long running conversation. While it may seem intrusive, it is rarely noticeable and is actually a good thing as we will see later.
Once we start a long running conversation, any conversationally scoped data that is available in the conversation at the point the conversation started, as well as any conversational scoped data we add to the conversation, will remain in that conversation until it ends.
Conversations can be started in a number of ways, by
calling methods on Seam components annotated with a
@begin
, or by using page metadata in
pages.xml
or from a link using the
s:link
or
s:button
Seam JSF components which have a
propagation
value set to
true
.
First off, we’ll be using the same model as the Spring sample project. Regarding the Dao pattern of layering, Seam offers a few different ways of structuring your application. Seam tends towards using ‘thicker’ stateful objects, objects that hold references to stateful data as well as providing action methods on those objects.
Seam also comes with classes for the "Seam Application Framework" which is a built-in framework for creating simple persistence and query components. We’ll cover both options here since it really is almost two separate features. There’s also the question of whether we use EJB or POJOs since Seam supports both out of the box. The draw back to this is that even if you use POJOs, you still require an EJB 3.0 environment to run it, even if it is the embedded EJB environment in a non-EJB container.
Seam works on a pull model rather than a push model. When a context variable is requested, Seam searches for the variable in the available contexts such as request scope, page scope, the conversational scope, and then session and application scopes. If it does not find it, it looks into its internal metadata to see if a factory method is registered for the variable name. If so, the factory method is called, and the variable is put into whatever scope the factory method or outjection specified.
We’ll start with our list of projects, for which we will
create a
ProjectListActionBean
which will be an EJB. This bean will use a query for
projects, and will be used for other functions later on.
First we define an interface for the bean.
Example 3.1. EJB Local interface for our bean
@Local
public interface ProjectListAction {
List<Project> getProjects();
void remove();
}
We implement this interface in the class
ProjectListBean
and define it as a Seam component called
projectList
.
Example 3.2. Implementation of our stateful EJB
@Name("projectList")
@Scope(ScopeType.CONVERSATION)
@Stateful
public class ProjectListBean implements ProjectList, Serializable {
@Logger
private Log log;
@In
private EntityManager entityManager;
private List<Project> projects;
@SuppressWarnings("unchecked")
@Factory("projects")
public List<Project> getProjects() {
if (projects == null) {
log.debug("Getting projects");
projects = entityManager.createQuery("select p from Project p")
.getResultList();
}
return projects;
}
@Remove @Destroy
public void remove() {
}
}
Here we declare our
ProjectListBean
class to be a Seam component called
projectList
and it is declared as a stateful EJB. The
@Name
annotation is used to specify the name of the component
to Seam and the
@Stateful
annotation is used to indicate that it is a stateful
EJB. The
@Factory
annotation on the
getProjects()
method is used to declare a factory method in Seam. If a
variable called
projects
is requested and not found, Seam would call this method
and the results will be put into a context variable
called
projects
. The scope of the results depends on the scope
specified in the factory annotation if there is one. If
not, it defaults to the scope of the component that
contains the factory method (in this case,
conversational scope).
The entity manager is a special seam component that is
injected like other seam components. The
@Logger
annotated member is a Seam logger which is a standard
logger that is automatically injected without the
explicit code to obtain a logger per class type. It also
wraps calls to the actual logger and lets you use EL
syntax within the logged message such as
"Saving project #{project.title}"
and the values will be substituted. It is these touches
throughout Seam that make it a very cohesive framework.
For the projects page, we can use the same HTML from the Spring application.
Example 3.3. JSF page for displaying the projects
<h:dataTable value="#{projects}" var="v_project">
<h:column>
<f:facet name="header">ID</f:facet>
<h:outputText value="#{v_project.id}" />
</h:column>
<h:column>
<f:facet name="header">Title</f:facet>
<s:link value="#{v_project.title}" view="/projectView"
propagation="none">
<f:param value="#{v_project.id}" name="projectId" />
</s:link>
</h:column>
<h:column>
<s:link value="Edit" view="/projectEdit" propagation="none">
<f:param value="#{v_project.id}" name="projectId" />
</s:link>
</h:column>
</h:dataTable>
When we use
s:link
or
s:button
Seam JSF controls, we are using controls that use GET
instead of POST. They also provide us with conversation
management attributes which is what the
propagate
attribute is. Since each page runs in a conversation,
even if it is a short lived one, there is a chance that
the next page will re-use the same conversation as the
last one. By setting propagation to none, we specify
that the conversation is not shared between the two
pages, and a new one is required for the new page. Also,
as this was typed out the code complete features in the
page editor and the WYSIWYG editor enabled me to quickly
pick my backing bean names.
Also note that we are able to use code completion on JSF
tags, so we just enter "h:output" it will suggest a
number of options. If we select the
outputText
element, it will add a value attribute to put the text
value into. This value attribute also has auto
completion, just like almost every other attribute and
JSF element . The Exadel and JBoss Tools team have done
an exceptional job with these tools.
If we start the server and go to our page, we will see a list of our projects. If you make a slight change to the page and refresh the page, it will take a couple of seconds for the change to be published to the server. It is an annoyance, but it is tempered slightly by the fact that you can do a preview in the IDE so you don't have to launch it in the browser to see every mistake you've made. For some reason, this delay seems longer on my machine than others I have seen.
You will notice that to list the projects, we haven’t
used any kind of flows yet. As we discussed earlier,
every page runs in a conversation even if it is one that
only lasts from this page to the next. Furthermore, we
haven’t declared any DataModels yet. For now, we can use
page parameters to pass the projectId over to the edit
page. To make this easier, we use some Seam provided JSF
controls. The
s:link
and
s:button
components provide us with a link and button component
that we can use to call pages, and pass parameters
easily. These requests are done as GET requests as
opposed to a POST that JSF would normally use.
In our JSF page, we define the column for the edit link
using a Seam
s:link
component.
Example 3.4.
Column for the edit link in
projects.xhtml
<h:column>
<s:link value="Edit" view="/projectEdit" propagation="none">
<f:param value="#{v_project.id}" name="projectId" />
</s:link>
</h:column>
When rendered in a page, this becomes
/projectEdit.seam?projectId=3
and acts as a GET request into
projectEdit.xhtml
. The reason we have the
propagation="none"
attribute is because by default, the
s:link
and
s:button
components propagate the conversation to the next page.
In this case, we don't really want to. We want the edit
page to grab a fresh instance of the project we are
about to edit. We use the same attribute in the view
link in the project title column.
Now lets look at the view and edit page, starting with the view page. We can just whip one up with a simple panel grid.
Example 3.5. Simple JSF project viewer
<h:panelGrid columns="2">
Project Id : <h:outputText value="#{project.id}" />
Title : <h:outputText value="#{project.title}" />
</h:panelGrid>
Now lets consider the problems we face to generate the
variable
project
. We need to take the parameter, and load the project
based on the parameter and push it out as a scoped
variable called
project
.
We'll do this the most direct way for now to give you an idea of how straightforward dealing with Seam components can be.
We'll create a new class called
ProjectHomeBean
which we will use to handle the parameter and the
instance of the project. First we define a local
interface for it called
ProjectHome
.
Example 3.6.
ProjectHome
interface for handling project instances.
@Local
public interface ProjectHome {
Project getProject();
void setProject(Project project);
Long getProjectId();
void setProjectId(Long id);
public void remove();
String save();
String cancel();
}
Next, we create a class called
ProjectHome
which implements this interface. If we wanted to make
this a javabean POJO, we could omit the interface and
the stateless annotation and Seam would use this bean
the same. You would however have to add transaction
annotations similar to the Spring bean ones.
Example 3.7.
ProjectHome
implementation
@Name("projectHome")
@Scope(ScopeType.CONVERSATION)
@Stateful
public class ProjectHomeBean implements ProjectHome {
@In
private EntityManager entityManager;
@Logger
private Logger log;
private Long projectId;
private Project project;
private boolean hasProjectId() {
return (projectId != null && projectId != 0);
}
@Factory("project")
public Project getProject() {
if (project == null) {
if (hasProjectId()) {
project = entityManager.find(Project.class, projectId);
} else {
project = new Project();
}
}
return project;
}
public Long getProjectId() {
return projectId;
}
public String save() {
entityManager.persist(project);
entityManager.flush();
FacesMessages.instance().add(
"Saved changes to project #{project.title}");
return "save";
}
public void setProject(Project project) {
this.project = project;
}
public void setProjectId(Long id) {
this.projectId = id;
}
public String cancel() {
entityManager.refresh(project);
return "cancel";
}
@Remove @Destroy
public void remove() {
}
}
Notice that we added a
@Factory
annotation onto the
getProject()
method. This annotation marks this method as the one to
call anytime we need a variable called
project
.
When we save the project, we also add a
FacesMessage
in code. Notice how we are able to use the EL expression
#{project.title}
in the message. We'll add a message for the cancel
method an alternate way.
In order to load the project we need to somehow bind the
projectId
parameter with the
projectId
member in this component. We can do this using
pages.xml
.
pages.xml
is one (or more) xml files for providing additional meta
data about each page to Seam. When you create an
application using the Seam-Gen tools or JBDS, Seam
creates an instance of this file with the default
exception handling information included to which you can
add your page information. Alternatively, you can have
multiple files that deal with logical sections of your
application. The information contained can related to
page parameters, conversations and page flows that start
or end on a given page, page navigation information for
a given page, actions to execute when a certain page is
requested, and even whether a login is required for
access to certain pages which can be defined using
wildcards.
Example 3.8.
Project view page parameter configuration from
pages.xml
<page view-id="/projectView.xhtml">
<param value="#{projectHome.projectId}" name="projectId"/>
</page>
This tells Seam that every time is renders this page, it
sets the value of
#{projectHome.projectId}
to the value of the
projectId
parameter. This binding is two way, in that when we move
to the next page, we put the value of
#{projectHome.projectId}
in a parameter called
projectId
and send it to the next page .
If we load our project view page, we can see the project is loaded correctly. If we click the browser refresh, we can see from the log that the page gets a fresh instance of the project entity which is good. We refresh the page to see the latest instance, not just to be sent the same output. Now let's consider the buttons to navigate to the edit page, or back to the project list.
Example 3.9.
JSF for the back and edit buttons in
projectView.xhtml
<h:panelGrid columns="2">
<s:button value="Back" view="/projects.xhtml" />
<s:button value="Edit" view="/projectEdit.xhtml">
<f:param name="projectId" value="#{project.id}" />
</s:button>
</h:panelGrid>
Here, we explicitly define the views we are going to with any parameters needed. This is one way of handling navigation. Because conversations are omnipresesnt, we don't have to rely on conversation mechanisms for passing data around, we can go back to page parameters. This is also beneficial because we can easily create loosely coupled pages. The edit page itself is fairly simple and is almost identical to the view page.
Example 3.10.
projectEdit.xhtml
page.
<h:panelGrid columns="2">
Project Id : <h:outputText value="#{project.id}" />
Title : <h:inputText value="#{project.title}" />
</h:panelGrid>
The next question to ask is where does the
project
variable come from this time? The answer is simple, we
just re-used the same code we used for the viewer. When
the page is loaded and JSF looks for a variable called
project
, Seam will use the same factory method to create the
project using the
projectId
parameter. We can deploy this page and run it right away
without any further coding. This is one of the benefits
of Seam variables being defined application wide.
We can also access this page directly with a url using
parameters such as
/projectEdit.seam?projectId=2
.
Let's consider the save and cancel buttons and how we act on them, and navigate to our next page based on the button clicked. We define our two buttons with actions calling the methods on the backing beans.
Example 3.11.
The save and cancel buttons in
projectEdit.xhtml
<h:panelGrid columns="2">
<h:commandButton value="Save" action="#{projectHome.save}"/>
<h:commandButton value="Cancel" action="#{projectHome.cancel}"/>
</h:panelGrid>
Our save button calls the
projectHome.save()
method which saves the project. The cancel button calls
the cancel method which simply refreshes the value of
the project we are editing. These methods return strings
for the navigation handlers to process.
Notice that we switched from using
s:button
to
h:commandButton
. Remember
s:button
and
s:link
perform a GET request without the POST. If we used the
seam components we wouldn't post our changes back to the
server. Technically, we could use a
s:button
for the cancel button since we don't care whether the
changes are posted back. Notice that we use the
immediate="true"
attribute for the command button since this bypasses the
validation steps which we don't care about since we are
cancelling changes.
Normally, in JSF, navigation is handled using the JSF
navigation rules. Seam offers us multiple additional
ways to handle navigation. The first is in the JSF
faces-config.xml
which I've not used for a while. Next we have
pages.xml
which is Seam specific. We can simply add on the
navigation rules to our page information in
pages.xml
Example 3.12.
Navigation using
pages.xml
<page view-id="/projectEdit.xhtml">
<begin-conversation join="true" flush-mode="manual"/>
<param value="#{projectHome.projectId}" name="projectId"/>
<navigation>
<rule if-outcome="save">
<end-conversation/>
<redirect view-id="/projectView.xhtml">
<param name="projectId" value="#{project.id}"/>
</redirect>
</rule>
<rule if-outcome="cancel">
<end-conversation/>
<redirect view-id="/projectView.xhtml">
<message>Cancelled Changes to project #{project.title}</message>
<param name="projectId" value="#{project.id}"/>
</redirect>
</rule>
</navigation>
</page>
This page definition does a number of things. When we
enter this page, we want to start a conversation. Since
we specified the
join="true"
attribute, then if one already exists we join that
existing conversation.
We then have some navigation rules, one for
save
and the other for
cancel
which were the two outcomes from the save and cancel
methods called from our
projectEdit.xhtml
page. If the user saves the project,
save
is returned to the navigation handler so we end the
conversation and redirect to the view page passing in
the project Id as a parameter. If they cancel the
changes, the cancel method is called which returns
cancel
. Navigationally, the same thing happens except we also
pass a message along with it. This message is rendered
on the page that we are redirected to. Notice how we
were able to include the EL expression
#{project.title}
into the message to give the name of the project we were
editing. The only reason the message is here is to
demonstrate the number of different ways we can do
simple things with Seam.
If we run this, edit a project, and cancel the changes we get the message appearing once we have cancelled the changes.
We came back to the view page with a message to display.
If we edit the project and save the changes, the message
is added from the
saveProject()
method in our bean.
Before we move on, let's consider one more scenario, the
adding of a new project to the list. We do this by
navigating to the
projectEdit
page with no
projectId
parameter. By default, we create a new project in our
project
factory method if no parameter is passed in to the bean.
We can add the create button on our projects page.
Example 3.13.
The add project button on the
project.xhtml
page.
<s:button value="New Project" view="/projectEdit.xhtml" propagation="none"/>
This was pretty straight forward, and our
projectHome
component has handled the cases where we want to view,
edit or create a new project instance giving us quite a
bit of code re-use without any additional code or
configuration needed. All we need to do is request the
project
variable and Seam will provide it for us. One real
benefit here is that we can move the factory method from
one bean to another and Seam will automatically use that
bean instead. We have created a de-coupling between our
view and the source from which we obtain data. To change
the source of our
project
entity in spring would require us changing all the
spring web flows that called that one method to generate
the project.
This section introduces a couple of generic components which can be used to build typical CRUD querying and entity handling functions. Note that these components make up the Seam Application Framework, which is just a small (and optional) part of the Seam framework itself. These are really just helper classes that make it really easy to build CRUD and query components.
Seam typically blurs the lines on typical application layering. Our Dao layer has become a couple of beans which hold state, and are themselves stateful. You can choose to create a Dao or service layer and inject it into beans that manage state, but it is somewhat unnecessary. Our data is decoupled from our view through the use of EL. Some people might be wondering where the application layers are and having fits at the notion of having a single bean handle view events and data access. There are good reasons why it makes sense to flatten things down since the flatter version handles 95% of situations, and it is simple to refactor out these pieces if you need to re-use them or access them as separate layers. Also, since the flatter version requires almost no code, it raises the question of whether code that is never written can be shareable?
Seam embraces this notion of thicker stateful beans
wholly to the extent of providing generic versions of
beans called
EntityHome
and
EntityQuery
. These components make up the Seam Application
Framework and are used to provide simple and easy
classes to fetch, create and persist entities and to
query for entity instances respectively. First, we’ll
take a look at the
EntityQuery
as we determine how we are going to display the list of
issues for a project. To display the list of issues, we
add a data table that connects to our list of issues.
Example 3.14. Issues data table
<h:dataTable value="#{issues}" var="v_issue">
<h:column>
<h:outputText value="#{v_issue.id}" />
</h:column>
<h:column>
<s:link value="#{v_issue.title}" view="/issueView.xhtml">
<f:param value="#{v_issue.id}" name="issueId" />
</s:link>
</h:column>
<h:column>
<s:link value="edit" view="/issueEdit.xhtml">
<f:param value="#{v_issue.id}" name="issueId" />
</s:link>
</h:column>
</h:dataTable>
Now we just need to determine how we obtain the value of
issues
one easy way would be to create a factory method in
components.xml
which is the configuration file used to define Seam
components in XML instead of annotations. We can also
use this file to specify factory methods which we do
here for the value
issues
.
Example 3.15.
Define
issues
factory in
components.xml
<factory name="issues" value="#{project.issues}" scope="conversation"/>
When we open the
projectView
page, since issues is not defined, Seam evaluates the
expression
#{project.issues}
and puts the results into a Seam context variable called
issues
in the chosen (or default) scope. Note that we don't
have any problems regarding Lazy Initialization
Exceptions, because Seam gives us two transactions
during the request - reponse process. The first
transaction spans the application invokation while the
second spans the render response phase of the JSF
lifecycle. This way, any problems that might occur from
a commit happen during the application invokation and
not after the response render where you just rendered a
message to the user that everything is ok.
Factory methods are a really nice way to let us create
ways to access data in a quick and dirty fashion. For
smaller result sets, this may be acceptable, but for
many cases, we want to use a method that is more
controllable, and lets us use pagination on the returned
data. For this, we could create a method on a Dao to
return the set of results based on the current page, and
the page size, or we can use an
EntityQuery
. The
EntityQuery
has methods for managing ordering and pagination
built-in and can be used by each query.
For now, we will define our entity query in
components.xml
.
Example 3.16.
Issue query defined in
components.xml
<fwk:entity-query name="issuesQuery" max-results="10" scope="CONVERSATION">
<fwk:ejbql>from Issue</fwk:ejbql>
<fwk:restrictions>
<value>project.id = #{project.id}</value>
</fwk:restrictions>
</fwk:entity-query>
This query creates a list of issues that belong to the
project identified by
#{project}
by matching the
id
attribute of the
project
, again by making use of EL expressions to great effect.
If we were to add pagination to our list of issues, we
could use the EntityQuery methods for
nextExists()
and
previousExists()
, and call the
next()
and
previous()
methods on our query. My article on
codeless ordered and paginated tables
describes how to use the entity query to great effect in
producing highly interactive tables.
Since we are driving our list of issues by the value of
#{project}
we need to make sure this value is valid on each refresh
which with the current implementation, it is. However,
it does raise a problem with Seam, we define our
issues
query in terms of the current value of
project
. As developers, we need to be mindful that anytime we
use this query, we need to have a valid value for
project
. In most cases, it shouldn't be a problem, but for
larger applications, I can see some confusion in keeping
track of dependencies. Compare this to Spring where the
query and the project id is local to the flow as opposed
to globally declared. If the project value is not
defined, the restriction will not be included in the
query and all issues will be returned by the query.
Now let's consider the edit/view pages for the issues.
This time we'll use the
EntityHome
piece of the Seam framework so you can see how simple it
can be to set up entity management using it. This is not
required for using Seam, but is simply a generic entity
manager that just contains the code that you would
typically end up writing anyway. Early examples for Seam
do not use these objects, nor did early versions of the
Seam-Gen tools. However, this method of creating
applications is becoming almost the standard with Seam.
The code is very similar to the code we wrote for the
ProjectHome
bean class, except that the
EntityHome
code is already written and ready for us to use in our
subclass. First, lets take a look at our page code that
will use our entity.
Example 3.17.
issueView.xml
code.
<h:panelGrid columns="2">
Issue Id : <h:outputText value="#{issue.id}" />
Title : <h:outputText value="#{issue.title}" />
Project : <h:outputText value="#{issue.project.title}" />
Status : <h:outputText value="#{issue.status.title}" />
Description : <h:outputText value="#{issue.description}" />
</h:panelGrid>
<h:panelGrid columns="2">
<s:button value="Back" action="back" />
<s:button value="Edit" action="edit" />
</h:panelGrid>
Now let's consider where the
#{issue}
value is coming from. Since we are using
EntityHome
, we can define it in
components.xml
using XML.
Example 3.18.
Defining
issueHome
and a factory for
issue
in
components.xml
<fwk:entity-home name="issueHome"
entity-class="org.issuetracker.model.Issue"
scope="conversation" />
<factory name="issue" value="#{issueHome.instance}" scope="conversation"/>
This sets up an
EntityHome
component called
issueHome
which will handle the persistence for a class of type
org.issuetracker.mode.Issue
. We also defined a factory method to be called when the
issue context variable doesn't exist. We simply get the
value of
issueHome.instance
which will return the instance of the
Issue
entity we are currently working with. Note that this all
uses lazy initialization, so if the
issue
variable doesn't exist, Seam calls the factory method
for it which is
#{issueHome.instance}
. If
issueHome
doesn't exist, then it is created as per our definition
in
components.xml
. When the
instance()
method is called, if the value held by this
EntityHome
component is null, it attempts to load one if it has an
Id
set, or create a new one if it doesn't.
Alternatively, we could write this easilt in code using the following
Example 3.19.
Java code implementation of
IssueHome
.
@Name("issueHome")
@Scope(ScopeType.Conversation)
public IssueHome extends EntityHome<Issue> {
public Long getIssueId() {
return (Long) getId();
}
public void setIssueId(Long id) {
setId(id);
}
@Factory("issue")
public Issue getIssue() {
return getInstance();
}
}
This few lines of code lets us implement the same thing
we did in xml. The benefits here is that the Ide has
better auto-completion when you use the code version,
you can provide a type for the id so you no longer need
to add the converter in pages.xml for assigning
parameters, and you can optionally add backing bean
methods if you choose to. Note that we also put the
factory method here to generate the issue variable. You
can also add code to handle security issues in the event
that no issue id is supplied and the user doesn't have
rights to create an issue. In order to use the java code
mechanism, you will need to delete the definition from
components.xml
since they share the same component name.
The only other piece we need to deal with is passing the
issueId
parameter to the
issueHome
component so we know the id of the issue we are dealing
with. For this, we will use
pages.xml
to provide the parameter to the
EntityHome
.
Example 3.20.
Binding parameter to entity home
id
property in
pages.xml
.
<page view-id="/issueView.xhtml">
<param value="#{issueHome.id}" name="issueId" converterId="javax.faces.Long"/>
</page>
Let's consider our navigation for the back and edit
buttons. This time, lets determine the navigation from
the page by using action strings set on the buttons,
similar to what we used with Spring. In
pages.xml
, we can extend the page definition to include the
navigation.
Example 3.21.
Defining navigation for
issueView.xhtml
in
pages.xml
.
<page view-id="/issueView.xhtml">
<param value="#{issueHome.id}" name="issueId" converterId="javax.faces.Long"/>
<navigation>
<rule if-outcome="back">
<redirect view-id="/projectView.xhtml">
<param name="projectId" value="#{issue.project.id}"/>
</redirect>
</rule>
<rule if-outcome="edit">
<redirect view-id="/issueEdit.xhtml">
<param name="issueId" value="#{issue.id}"/>
</redirect>
</rule>
</navigation>
</page>
Here, we have defined the pages to go to when the back or edit buttons are clicked. Using this technique allows us to decouple the navigation from the view, with the navigation being determined based on the outcome from the view. This is important if we are using workflows since a 'back' button may take us to different places depending on the work flow we are currently in.
Now lets create the edit page. Note that again, we
re-use our
IssueHome
component to handle the Issue object persistence tasks
for us. We just need to provide the
issueId
in the parameter.
For the page itself, we will use something a little different that comes with Seam in order to handle layout this time.
Example 3.22.
issueEdit.xhtml
page using the seam decorator layout components
<s:decorate template="/layout/edit.xhtml">
<ui:define name="label">Issue Id : </ui:define>
<h:outputText value="#{issue.id}" />
</s:decorate>
<s:decorate template="/layout/edit.xhtml">
<ui:define name="label">Title : </ui:define>
<h:inputText value="#{issue.title}" required="true" />
</s:decorate>
<s:decorate template="/layout/edit.xhtml">
<ui:define name="label">Project : </ui:define>
<h:outputText value="#{issue.project.title}" />
<h:commandLink action="selectProject" value="change" />
</s:decorate>
<s:decorate template="/layout/edit.xhtml">
<ui:define name="label">Status : </ui:define>
<h:selectOneMenu value="#{issue.status}">
<s:selectItems value="#{issueStates}" var="v_status"
label="#{v_status.title}" />
<s:convertEntity />
</h:selectOneMenu>
</s:decorate>
<s:decorate template="/layout/edit.xhtml">
<ui:define name="label">Description : </ui:define>
<h:inputTextarea value="#{issue.description}" />
</s:decorate>
<s:div styleClass="buttonSet">
<h:panelGrid columns="2">
<h:commandButton value="Save" action="#{issueHome.update}" />
<h:commandButton value="Cancel" action="#{issueHome.cancelChanges()}"/>
<h:commandButton value="dirtys" action="dirty" />
</h:panelGrid>
</s:div>
Well this looks a bit different! This uses the
s:decorate
tag that Seam provides. It lets you use a Facelet
template to decorate a JSF tag. It extends the Facelets
decorate tag by making the values
#{invalid}
and
#{required}
available inside of the template . These values
represent whether the input control has a validation
error or if it is required. This way we can display the
field differently if it is invalid or is required. You
can easily write your own
edit.xhtml
template or use the Seam version. In this case, the Seam
version uses CSS to position the form controls. The
decorator also automatically wraps the control in Seam
validation tags which automatically performs validation
using the hibernate validators on the model. This reuse
of validation annotations is another strong point for
Seam.
Besids the strange layout method, this form is fairly straightforward. However, the one piece we should take special note of is the drop down selection for the issue status values.
Example 3.23.
Status selection using Seam
selectItems
components.
<s:decorate template="/layout/edit.xhtml">
<ui:define name="label">Status : </ui:define>
<h:selectOneMenu value="#{issue.status.title}">
<s:selectItems value="#{issueStates}" var="v_status"
label="#{v_status.title}" />
<s:convertEntity />
</h:selectOneMenu>
</s:decorate>
Seam provides JSF tags to allow you to bind drop down
lists or listboxes to entity lists without requiring you
to wrap them in JSF select items. It also provides a
converter that uses the default
entityManager
to convert the selection to an entity. This makes lookup
lists from entities really simple to implement. In order
for this to work, I need to create a list of the issue
status values called
issueStates
. I could use an Entity Query defined in
components.xml
.
Example 3.24.
EntityQuery
for grabbing a list of the issue status objects.
<fwk:entity-query name="issueStatusQuery">
<fwk:ejbql>from IssueStatus</fwk:ejbql>
</fwk:entity-query>
<factory name="issueStates" value="#{issueStatusQuery.resultList}"/>
Instead of "programming in XML", I'm going to create the
same class in code by writing a class that extends the
EntityQuery
the same way we did for the
IssueHome
bean.
Example 3.25.
Implementation of the
issueStatusQuery
.
@Name("issueStatusQuery")
@Scope(ScopeType.CONVERSATION)
public class IssueStatusQuery extends EntityQuery<IssueStatus> {
@Override
public String getEjbql() {
return "from IssueStatus";
}
@Factory("issueStates")
public List<IssueStatus> getIssueStatuses() {
return getResultList();
}
}
It takes only a little more code to define this in Java as opposed to using XML, and we get exactly the same result. Again, the benefit to doing it here is that we can add additional methods on there, as well as type safety, and also better auto completion in the IDE.
One last thing we need to add is to map the
issueId
parameter to the
issueHome.id
value for the
issueEdit.xhtml
page. This is a step I usually forget until I go to the
page and am greeted with a blank page.
Example 3.26.
pages.xml
parameter and conversation definition for
issueEdit.xhtml
.
<page view-id="/issueEdit.xhtml">
<begin-conversation/>
<param value="#{issueHome.id}" name="issueId"
converterId="javax.faces.Long"/>
</page>
The drop down is selecting the current value correctly, and as we'll see, it will put any newly selected value back in the model. Let's deal with the save and cancel buttons.
This time, we'll use the string results from the actions to perform navigation. Also, bear in mind we will later be adding the code to select which project this issue belongs to. For this reason, we'll make this process a work flow, or as Seam calls them page flows which is the third and final mechanism we have for handling navigation.
We'll create the pageflow file called
issueEdit.jpdl.xml
.
Example 3.27.
issueEdit.jpdl.xml
page flow for editing the issue.
<?xml version="1.0" encoding="UTF-8"?> <pageflow-definition xmlns="http://jboss.com/products/seam/pageflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.com/products/seam/pageflow http://jboss.com/products/seam/pageflow-2.0.xsd" name="issueEdit"> <start-page view-id="/issueEdit.xhtml" name="issueEdit"> <transition name="updated" to="endState"/> <transition name="cancel" to="endState"/> </start-page> <page name="endState" view-id="/issueView"> <end-conversation /> </page> </pageflow-definition>
In order to incorporate this pageflow into the Seam
application, we need to add it to
components.xml
in the place defined in the default version of the file.
Example 3.28.
Adding the pageflow to
components.xml
<bpm:jbpm>
<bpm:pageflow-definitions>
<value>issueEdit.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm>
In
pages.xml
we alter the begin conversation attribute to include the
name of the page flow to use when we start the
conversation. The page flow typically operates as part
of a conversation, with all data in the page flow being
conversational data held in the conversation.
Example 3.29. Invoking the page flow when the conversation is started.
<page view-id="/issueEdit.xhtml">
<begin-conversation join="true" pageflow="issueEdit" flush-mode="manual"/>
<param value="#{issueHome.id}" name="issueId" converterId="javax.faces.Long"/>
</page>
We also specify that the
flush-mode
on this conversation is set to manual which means that
changes to our entities are not flushed until we
manually call the flush method. This is similar to
adding the persistence context tag to the Spring flows
making the PC last for the duration of the conversation.
We add two buttons in
issueEdit.xhtml
for saving and cancelling changes.
Example 3.30.
JSF for the save and cancel buttons in
issueEdit.xhtml
<h:commandButton value="Save" action="#{issueHome.update}" />
<h:commandButton value="Cancel" action="cancel"/>
When the user clicks the save button, we call the update
method of the
issueHome
component. This persists our changes and flushes the
persistence context. It also adds a faces message
indicating the instance has been saved. The update
method returns a string of
updated
which we respond to in the pageflow. In the event of an
updated
or
cancel
string action, we navigate to the
endState
which is the issue view page.
Notice that we don't pass any parameter to the issue
view page used for the end state. The reason is because
even though we end the conversation, the conversation is
re-used in the next page , so all the context variables
of the conversation are still available when we get to
the issue view page. This means we don't need to re-load
the issue to view it since there is already a context
variable called
issue
. Besides the fact that I'm not a fan of this implicit
passing of data, it also can be problematic. What if we
made changes to the issue that did get posted back to
the server as part of the user interaction and the
current instance of the
issue
entity is dirty? We need to refresh it somehow. We can
actually view this problem in action by adding a button
that does nothing on the edit page. If you edit the
issue title, click the button (which posts changes but
stays on the page), click cancel, then the issue view
page displays the changed issue with the wrong title.
Fixing this problem is simple - kind of. We need to
either invalidate both the
issue
variable and the value of
issueHome.instance
which is where it will be obtained from when it is not
found. Remember, our home bean is stateful so even
though the
issue
variable can be invalidated, it is pointless if Seam is
just going to fetch the dirty copy from
issueHome.instance
via the factory method.
Alternatively, we can just refresh the current instance
of the
issue
we are using. . The other option is to use events.
Events can be raised from numerous places, either via
code or as part of the page flow or
pages.xml
navigation. We might raise an event for when we cancel
changes to the Issue. We could then outject a null
issue
value or just set it in code, and invalidate the issue
instance held in the
issueHome
component. However, it seems a little complex to create
event listeners for every kind of entity we want to
edit.
Unfortunately, the default implementation of
EntityHome
does not contain a
refresh
or
cancelChanges
method in which we can do any of this. We can extend the
EntityHome
class to include this, you can use your own version
instead of the built in version because the framework
tags have a
class
attribute that lets you specify the class to use for the
EntityHome
.
To do this, we create class called
EntityHomeExtended
which extends the
EntityHome
.
Example 3.31.
Code listing for
EntityHomeExtended
public class EntityHomeExtended<E> extends EntityHome<E> {
public String cancelChanges() {
getEntityManager().refresh(getInstance());
return "cancel";
}
}
We could have expanded on this so it follows the
standards set in the
persist()
and
update
methods with regards to raising events and putting
messages in the
FacesMessages
. To use our new class, we just change the
issueHome
definition in
components.xml
.
Example 3.32.
Framework
EntityHome
using our extended class.
<fwk:entity-home class="org.seamtracker.session.EntityHomeExtended"
name="issueHome"
entity-class="org.issuetracker.model.Issue"
scope="conversation" />
Alternatively, if you used the java code version of
IssueHome
, we just extend it from the
ExtendedEntityHome
. Finally, we amend our page to call the new method when
the user clicks cancel.
Example 3.33. JSF for the cancel button with our new method
<h:commandButton value="Save" action="#{issueHome.update}" />
<h:commandButton value="Cancel" action="#{issueHome.cancelChanges()}"/>
Now we can make changes, click cancel, and any changes to the issue will be undone with the entity being refreshed by our new method.
Now let's look at the issue of changing the project for
the issue. First, let's add a link to change the
project. We put this next to the project name display in
the
issueEdit.xhtml
.
Example 3.34.
Adding link to
issueEdit.xhtml
to select a project.
<h:outputText value="#{issue.project.title}" />
<h:commandLink action="selectProject" value="change" />
Now we need to consider a few things. Until now, we have
not used
DataModel
in our pages, mainly because we haven't needed to since
we have been using parameters for passing data around.
In order to select a project, we probably want to use a
DataModel
to create a clickable list. We also need to have some
kind of flag that will let us indicate on the project
list page that we want to select a project. Since we
have our
ProjectListBean
, we can use that to hold the flag since the whole bean
is stateful. We add some methods to the interface to
handle these tasks.
Example 3.35.
Additional interface methods for project selection
on the
projectList
interface.
Boolean getDoSelect(); void setDoSelect(Boolean doSelect); public Project getSelectedProject(); public void setSelectedProject(Project selectedProject);
We then implement these methods on the
projectListBean
.
Example 3.36.
Implementation of project selection code in
projectListBean
.
private Boolean doSelect;
@DataModelSelection
private Project selectedProject;
public Boolean getDoSelect() {
return doSelect;
}
public void setDoSelect(Boolean doSelect) {
this.doSelect = doSelect;
}
public Project getSelectedProject() {
return selectedProject;
}
public void setSelectedProject(Project selectedProject) {
this.selectedProject = selectedProject;
}
//--pre existing code
@Factory("projects")
@DataModel
public List<Project> getProjects() {
The
doSelect
flag is used to determine whether the projects page is
displayed in selection mode, or normal browsing mode. As
we added a
@DataModel
annotation to the
getProjects
method, when Seam fetches the list of projects it wraps
the list in a
DataModel
, and we can have a list of entities which can be used
as clickable links. When a
DataModel
row link is clicked, Seam can automatically push the
entity into a property which has been annotated with the
@DataModelSelection
. By default the single
DataModelSelection
is populated from the single
DataModel
clicked item. If you have more than one
DataModel
on a bean then Seam cannot assume the defaults, and you
have to start naming the data models and the associated
datamodel selections.
In our pageflow, we add the transition and the page for selecting the project, and the method call to set the project value on the issue entity.
Example 3.37. Adding the transition for selecting a project for the issue.
<start-page view-id="/issueEdit.xhtml" name="issueEdit">
<transition name="updated" to="endState" />
<transition name="cancel" to="endState" />
<!-- new transition -->
<transition to="projectState" name="selectProject">
<action expression="#{projectList.setDoSelect(true)}" />
</transition>
</start-page>
<page name="projectState" view-id="/projects.xhtml">
<transition name="cancelSelection" to="issueEdit" />
<transition name="selected" to="issueEdit">
<action expression="#{issue.setProject(projectList.selectedProject)}"/>
</transition>
</page>
When we click on the select project link, we set the
doSelect flag on the
projectList
bean to true. When we get to the projects page, we use
the
projectList
bean that we created in the issue edit page to display
the projects. When displaying the projects, we query the
select flag to determine whether the projects can be
selected. If so, we display the select link in a column
in the data table.
Example 3.38. Column for selecting a project in the table.
<h:column rendered="#{projectList.doSelect == true}">
<f:facet name="header">Select</f:facet>
<s:link value="Select" action="selected"/>
</h:column>
We only render the column if the
doSelect
flag is set and the link returns an action of
selected
. Going back to our page flow, in the
projectState
state, we have a transition for
selected
that sets the project for the current issue to the
selected project value in the
projectList
bean.
Example 3.39. Transition to set the project when selected
<transition name="selected" to="issueEdit">
<action expression="#{issue.setProject(projectList.selectedProject)}"/>
</transition>
If the action is
selected
then we first call the
setProject()
method on the issue, and set it to the current
selectedProject
on the
projectList
bean. We then transition to the
issueEdit
node which is the
issueEdit
page.
One annoying problem I found with Seam pageflow is that I had a couple of errors in my code which resulted in an endless loop for the navigation handler. Not only is it annoying but it is hard to pull out the real cause of the problem since it gets scrolled off the console display if it is displayed at all.
The approach the Seam team took to letting you interface with the pieces of the Seam Framework was to instantiate them as Seam components just like the components you write. That means, as the manual says, you get to work with "one kind of stuff". For example, in your web page, you can optionally render a 'debug panel' which includes all sorts of framework related information such as:
These components are also easily available in code by either injecting them, or using the static access method. Just about every piece of the Seam Framework is accessible using these three methods which makes the Framework very accessible.
@In Private Conversation conversation
Or using :
PageFlow.getInstance();
Since these components are exposed via EL, and Seam tries to work in EL in almost every place it can (pageflows, messages, debug log messages, queries, sql) you can access these components from just about anywhere. Also, because the tooling the IDE team have built gives you plenty of EL code completion, the "one kind of stuff" rule means you also get auto-completion on these framework components.
Additionally, the system components can be replaced with your own versions if you chose to. There is precedence to component installation which lets you override the default implementation with your own. This component precedence is also used to instantiate Mock or test components when running tests instead of the actual components.
Seam comes packed with a bunch of additional features from PDF and email generation using JSF tags to Seam Remoting. It extends the EL expressions used in JSF to allow the use of parameters in calls. It includes JSF components that provide GET request features as opposed to JSF's POST only implementation. It comes with the ability to integrate Spring beans into your Seam application as first class Seam components, as well as the ability to integrate with GWT. Seam also allows you to create conversational web services in which you can hold a stateful conversation with the server from a variety of clients. Seam also allows for the injection of the POJO Cache as a Seam component, and also allows for the caching of rendered JSF content fragments with a single JSF tag. Seam comes bundled with Ajax4Jsf and the Richfaces JSF component set which gets you up and running and creating ajaxified applications in no time.
Seam also comes with Seam-Gen than not only generates the basic application, but can be used to generate forms and JPA entity objects from data tables as you want to add new forms. While this works pretty well, and can speed things up a bit and can get you going, there can be problems with different ideas on how tables should be named and we are getting into code generator territory which means you start losing your grip on how your app is written. However, the basic Seam-Gen function of creating an app that is ready to be deployed works very well and takes a minimalist approach to code generation.
The number of options avilable regarding security is lacking, but as of writing (June 2008) it is undergoing a serious overhaul and from first glances, it looks like it will be almost on par with Spring Security. Like most features of Seam, security is available from EL expressions, and also can be applied at the page level, method level, bean level or HTML component level.
Seam comes with a transaction manager that solves problems relating to committing changes and then needing a transaction when rendering a view. Typically with web frameworks if you use the Open Session In View solution to the lazy initialization exception, you have a transaction that spans from the start of the request to the end of the request when the response has rendered. However, what if you save data in the action, and render a success message only to find that you have an error when you commit the data in the transaction? If you commit the transaction after the bean action, you could get and LIE if you require additional pieces of the object graph to render the response. Seam's answer to this is to use two transactions. The first is used to wrap any persistence that takes place in the bean action. The second is used to provide any additional object loading in the rendering phase of the request. Also, if something fails in the update during the bean action, we find out before we are rendering a response, and we can actually let the user know.
Seam also provides process flow management so you can create work flows that last longer than any user session or conversation as well as incorporating JBoss Rules for defining business rules. However, I have had little experience with either of these two Seam functions.
Pages.xml
lets you define pages to default to in the case of
exceptions, as well as defining what actions to take on
a specific page if there is no active conversation, and
whether a conversation is required. This can take care
of the issue of the double submit problem since you
can't go back and reopen a conversation that closed when
you clicked submit the first time round.
Seam really does tie together a number of good functions and features in a single stack which is something I think java has been missing for a while. What's more, it isn't just a collection of distributed libraries, they are pulled in and made a part of the Seam framework. This was one of the goals behind the Seam framework, to make the whole far greater than the sum of it's parts. Yes, in other applications, you can use iText for PDF generation, but only Seam lets you access it through JSF components using the same Facelets and EL data access mechanisms that you have already set up. Seam embraces and provides plenty of value add ons for these libraries. Seam also appears to provide a lot more flexibility than Spring on how you get things done by offering multiple ways to do the same thing. The downside to this is that there is no 'best practices' way of doing things and some ways could be more problematic in the long run and as applications grow. The demos were mostly written before the Seam Application Framework was finalized, and the framework is now the recommended method while their Hotel booking example still focuses on passing objects around between beans which I am not a fan of.
JBoss worked with Exadel on making their JSF editor tools and Richfaces a part of the JBoss offerings, and includes seamless integration with Seam. Exadel have now also come out with Flamingo which lets you use Seam with Adobe Flex.
Seam has sophisticated conversation management using a
single parameter to keep track of the conversation for
each page. Seam also supports the notions of workspaces
whereby you can have multiple conversations running at
the same time and choose between them. Each conversation
can be given a description which can be used to
distinguish between the conversations. For example, if
you search for hotels, and decide to open up a booking
form in a new window for 4 of them, you create 4
workspaces that you can switch between. Each one has an
assigned description such as
Book Hotel #{hotel.name}
so the workspace description has meaning. You can also
use business conversation Ids so if your URL is
/projectEdit.seam?projectId=12
you can tell Seam to use the project Id value as the
conversation Id for a given page. The advantage here is
that if you navigate off that page, and go back to
/projectEdit.seam?projectId=12
you will end up back in the same conversation. Seam also
lets you assign a timeout value for conversations and if
needed, you can assign a different timeout value for
individual conversations. Tie this in with RESTful Urls
using URL rewriting which now comes with Seam, and you
can use
/projects/12/edit
as your URL and not only will it manage your
conversation for you, but it will give you a
bookmarkable URL with JSF.
Seam also has a great Events system which lets you
trigger actions in backing beans in response to events
raised by your application, or events fired by the Seam
framework itself. These events work on a subscriber
basis so adding another observer to the event is a
simple as annotating a Seam component with
@Observer([eventName])
. You can also raise your own events and add observers
to them so you could have a bean that receives a
specific event (i.e. closing a case) to send an email to
a supervisor. Data can also be passed in events so the
particular case can be passed to the event observer.
The other aspect of Seam is as a conversational backend for non-JSF front ends. I haven't really played with this that much, but there is a Wicket-Seam interface in the works, there have been a number of posts about getting GWT to work with Seam, and Exadel have released Flamingo which is their Seam interface for Adobe Flex. Seam lets you write conversational Web Services which is another way to get more mileage out of the code you write.
Seam Remoting lets you call your server side Seam Components from Javascript with a couple of bean annotations and javascript include files added to the web page. This opens up plenty of possibilities for creating lightweight sites that run mostly on the browser with only a minimal amount of server interaction. Not only can you call business bean methods but also gain access to your domain model from javascript. You can also use conversations in your interactions with the server.
| http://www.andygibson.net/ | Copyright © 2008 Andy Gibson |