|
|
1.1.3. Running the example tests
Most of the examples come with a suite of TestNG integration tests. The
easiest way to run the tests is to run ant testexample inside the examples/registration directory. It is
also possible to run the tests inside your IDE using the TestNG plugin.
1.2. Your first Seam
application: the registration example
The registration example is a fairly trivial application that lets a new
user store his username, real name and password in the database. The example
isn't intended to show off all of the cool functionality of Seam. However,
it demonstrates the use of an EJB3 session bean as a JSF action listener,
and basic configuration of Seam.
We'll go slowly, since we realize you might not yet be familiar with EJB
3.0.
The start page displays a very basic form with three input fields. Try
filling them in and then submitting the form. This will save a user object
in the database.
1.2.1. Understanding the code
The example is implemented with two JSP pages, one entity bean and one
stateless session bean.
Let's take a look at the code, starting from the "bottom".
1.2.1.1. The entity bean: User.java
We need an EJB entity bean for user data. This class defines
persistence and
validation declaratively, via
annotations. It also needs some extra annotations that define the class
as a Seam component.
Example 1.1.
@Entity (1) @Name("user") (2) @Scope(SESSION) (3) @Table(name="users") (4) public class User implements Serializable { private static final long serialVersionUID = 1881413500711441951L; private String username; (5) private String password; private String name; public User(String name, String password, String username) { this.name = name; this.password = password; this.username = username; } public User() {} (6) @NotNull @Length(min=5, max=15) (7) public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; } @NotNull public String getName() { return name; }
public void setName(String name) { this.name = name; } @Id @NotNull @Length(min=5, max=15) (8) public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
|
(1)
|
The EJB3 standard @Entity annotation indicates that the User
class is an entity bean.
|
|
(2)
|
A Seam component needs a component
name specified by the
@Name annotation. This name must be unique within the
Seam application. When JSF asks Seam to resolve a context
variable with a name that is the same as a Seam component
name, and the context variable is currently undefined
(null), Seam will instantiate that component, and bind the
new instance to the context variable. In this case, Seam
will instantiate a User the first time JSF encounters a
variable named user.
|
|
(3)
|
Whenever Seam instantiates a component, it binds the new
instance to a context variable in the component's
default context. The
default context is specified using the
@Scope annotation. The User bean is a session scoped
component.
|
|
(4)
|
The EJB standard @Table annotation indicates that the User class
is mapped to the users table.
|
|
(5)
|
name, password and username are the persistent attributes of the entity
bean. All of our persistent attributes define accessor
methods. These are needed when this component is used by JSF
in the render response and update model values phases.
|
|
(6)
|
An empty constructor is both required by both the EJB
specification and by Seam.
|
|
(7)
|
The @NotNull and @Length annotations are part of the Hibernate
Validator framework. Seam integrates Hibernate Validator and
lets you use it for data validation (even if you are not
using Hibernate for persistence).
|
|
(8)
|
The EJB standard @Id annotation indicates the primary key
attribute of the entity bean.
|
The most important things to notice in this example are the @Name and
@Scope annotations. These annotations establish that this class is a Seam
component.
We'll see below that the properties of our User class are bound to
directly to JSF components and are populated by JSF during the update
model values phase. We don't need any tedious glue code to copy data
back and forth between the JSP pages and the entity bean domain model.
However, entity beans shouldn't do transaction management or database
access. So we can't use this component as a JSF action listener. For
that we need a session bean.
1.2.1.2. The stateless session bean class:
RegisterAction.java
Most Seam application use session beans as JSF action listeners (you can
use JavaBeans instead if you like).
We have exactly one JSF action in our application, and one session bean
method attached to it. In this case, we'll use a stateless session bean,
since all the state associated with our action is held by the User bean.
This is the only really interesting code in the example!
Example 1.2.
@Stateless (1) @Name("register") public class RegisterAction implements Register {
@In (2) private User user; @PersistenceContext (3) private EntityManager em; @Logger (4) private Log log; public String register() (5) { List existing = em.createQuery( "select username from User where username=#{user.username}") (6) .getResultList(); if (existing.size()==0) { em.persist(user); log.info("Registered new user #{user.username}"); (7) return "/registered.xhtml"; (8) } else { FacesMessages.instance().add("User #{user.username} already exists"); (9) return null; } }
}
|
(1)
|
The EJB standard @Stateless annotation marks this class as
stateless session bean.
|
|
(2)
|
The
@In annotation marks an attribute of the bean as
injected by Seam. In this case, the attribute is injected
from a context variable named user (the instance variable
name).
|
|
(3)
|
The EJB standard @PersistenceContext annotation is used to inject the EJB3
entity manager.
|
|
(4)
|
The Seam @Logger annotation is used to inject the component's
Log instance.
|
|
(5)
|
The action listener method uses the standard EJB3 EntityManager API to
interact with the database, and returns the JSF outcome.
Note that, since this is a sesson bean, a transaction is
automatically begun when the register() method is called, and
committed when it completes.
|
|
(6)
|
Notice that Seam lets you use a JSF EL expression inside
EJB-QL. Under the covers, this results in an ordinary JPA
setParameter() call on the standard JPA Query object. Nice, huh?
|
|
(7)
|
The Log API lets us easily display templated log messages.
|
|
(8)
|
JSF action listener methods return a string-valued outcome
that determines what page will be displayed next. A null
outcome (or a void action listener method) redisplays the
previous page. In plain JSF, it is normal to always use a
JSF navigation rule to
determine the JSF view id from the outcome. For complex
application this indirection is useful and a good practice.
However, for very simple examples like this one, Seam lets
you use the JSF view id as the outcome, eliminating the
requirement for a navigation rule.
Note that when you use a view id as
an outcome, Seam always performs a browser
redirect.
|
|
(9)
|
Seam provides a number of built-in
components to help solve common problems. The FacesMessages
component makes it easy to display templated error or
success messages. Built-in Seam components may be obtained
by injection, or by calling an instance() method.
|
Note that we did not explicitly specify a @Scope this time. Each Seam
component type has a default scope if not explicitly specified. For
stateless session beans, the default scope is the stateless context.
Actually, all stateless session
beans belong in the stateless context.
Our session bean action listener performs the business and persistence
logic for our mini-application. In more complex applications, we might
need to layer the code and refactor persistence logic into a dedicated
data access component. That's perfectly trivial to do. But notice that
Seam does not force you into any particular strategy for application
layering.
Furthermore, notice that our session bean has simultaneous access to
context associated with the web request (the form values in the User
object, for example), and state held in transactional resources (the
EntityManager object). This is a break from traditional J2EE architectures.
Again, if you are more comfortable with the traditional J2EE layering,
you can certainly implement that in a Seam application. But for many
applications, it's simply not very useful.
1.2.1.3. The session bean local interface:
Register.java
Naturally, our session bean needs a local interface.
Example 1.3.
@Local public interface Register { public String register(); }
That's the end of the Java code. Now onto the deployment descriptors.
1.2.1.4. The Seam component deployment
descriptor: components.xml
If you've used many Java frameworks before, you'll be used to having to
declare all your component classes in some kind of XML file that
gradually grows more and more unmanageable as your project matures.
You'll be relieved to know that Seam does not require that application
components be accompanied by XML. Most Seam applications require a very
small amount of XML that does not grow very much as the project gets
bigger.
Nevertheless, it is often useful to be able to provide for
some external configuration of
some components (particularly the
components built in to Seam). You have a couple of options here, but the
most flexible option is to provide this configuration in a file called
components.xml, located in the WEB-INF directory. We'll use the components.xml file to tell Seam
how to find our EJB components in JNDI:
Example 1.4.
<?xml version="1.0" encoding="UTF-8"?> <components xmlns="http://jboss.com/products/seam/components" xmlns:core="http://jboss.com/products/seam/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.0.xsd http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd"> <core:init jndi-pattern="@jndiPattern@"/> </components>
This code configures a property named jndiPattern of a built-in Seam component
named org.jboss.seam.core.init. The funny @ symbols are there because our Ant build script
puts the correct JNDI pattern in when we deploy the application.
1.2.1.5. The web deployment description:
web.xml
The presentation layer for our mini-application will be deployed in a
WAR. So we'll need a web deployment descriptor.
Example 1.5.
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Seam -->
<listener> <listener-class>org.jboss.seam.servlet.SeamListener</listener-class> </listener>
<!-- JSF --> <listener> <listener-class>com.sun.faces.config.ConfigureListener</listener-class> </listener> <context-param> <param-name>javax.faces.DEFAULT_SUFFIX</param-name> <param-value>.xhtml</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.seam</url-pattern> </servlet-mapping> <session-config> <session-timeout>10</session-timeout> </session-config>
</web-app>
This web.xml file configures Seam and JSF. The configuration you see here is
pretty much identical in all Seam applications.
1.2.1.6. The JSF configration: faces-config.xml
Most Seam applications use JSF views as the presentation layer. So
usually we'll need faces-config.xml. In our case, we are going to use Facelets for
defining our views, so we need to tell JSF to use Facelets as its
templating engine.
Example 1.6.
<?xml version="1.0" encoding="UTF-8"?> <faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<!-- Facelets support --> <application> <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> </application> </faces-config>
Note that we don't need any JSF managed bean declarations! Our managed
beans are annotated Seam components. In Seam applications, the faces-config.xml is
used much less often than in plain JSF.
In fact, once you have all the basic descriptors set up, the
only XML you need to write as you add
new functionality to a Seam application is orchestration: navigation
rules or jBPM process definitions. Seam takes the view that
process flow and
configuration data are the only
things that truly belong in XML.
In this simple example, we don't even need a navigation rule, since we
decided to embed the view id in our action code.
1.2.1.7. The EJB deployment descriptor:
ejb-jar.xml
The ejb-jar.xml file integrates Seam with EJB3, by attaching the SeamInterceptor to all
session beans in the archive.
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd" version="3.0"> <interceptors> <interceptor> <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class> </interceptor> </interceptors> <assembly-descriptor> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class> </interceptor-binding> </assembly-descriptor> </ejb-jar>
1.2.1.8. The EJB persistence deployment
descriptor: persistence.xml
The persistence.xml file tells the EJB persistence provider where to find the
datasource, and contains some vendor-specific settings. In this case,
enables automatic schema export at startup time.
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="userDatabase"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/DefaultDS</jta-data-source> <properties> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> </properties> </persistence-unit> </persistence>
1.2.1.9. The view: register.xhtml and registered.xhtml
The view pages for a Seam application could be implemented using any
technology that supports JSF. In this example we use Facelets, because
we think it's better than JSP.
Example 1.7.
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:s="http://jboss.com/products/seam/taglib" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<head> <title>Register New User</title> </head> <body> <f:view> <h:form> <s:validateAll> <h:panelGrid columns="2"> Username: <h:inputText value="#{user.username}" required="true"/> Real Name: <h:inputText value="#{user.name}" required="true"/> Password: <h:inputSecret value="#{user.password}" required="true"/> </h:panelGrid> </s:validateAll> <h:messages/> <h:commandButton value="Register" action="#{register.register}"/> </h:form> </f:view> </body>
</html>
The only thing here that is specific to Seam is the <s:validateAll> tag. This JSF
component tells JSF to validate all the contained input fields against
the Hibernate Validator annotations specified on the entity bean.
Example 1.8.
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core">
<head> <title>Successfully Registered New User</title> </head> <body> <f:view> Welcome, #{user.name}, you are successfully registered as #{user.username}. </f:view> </body>
</html>
This is a boring old Facelets page using some embedded EL. There is
nothing specific to Seam here.
1.2.1.10. The EAR deployment descriptor:
application.xml
Finally, since our application is deployed as an EAR, we need a
deployment descriptor there, too.
Example 1.9.
<?xml version="1.0" encoding="UTF-8"?> <application xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" version="5"> <display-name>Seam Registration</display-name>
<module> <web> <web-uri>jboss-seam-registration.war</web-uri> <context-root>/seam-registration</context-root> </web> </module> <module> <ejb>jboss-seam-registration.jar</ejb> </module> <module> <ejb>jboss-seam.jar</ejb> </module> <module> <java>jboss-el.jar</java> </module>
</application>
This deployment descriptor links modules in the enterprise archive and
binds the web application to the context root /seam-registration.
We've now seen all the files in the
entire application!
|