Your first Seam application will be a very simple one where we do the following:
A name can be entered on a webpage and saved to a database
At any point, the user can press a button to go to a page which displays all the names currently saved
From this list page, names can be deleted or updated
The user can also go back to the entry page
We will not be using the Seam-Gen tools at this point, asides from the initial project creation outline in the previous step.
If you're not sure why we want to use EJB, I strongly suggest taking a quick break to brush up on it.
I'm not displaying the interfaces which the session beans are implementing. There's nothing of interest in them, with the possible exception that that are annotated with
@Local
; searching the Internet should provide many, many examples.
-
Create the Person entity
package org.domain.myProject.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.hibernate.validator.NotNull; import org.jboss.seam.annotations.Name; @Entity @Name("person") public class Person { private Long id; private String name; @Id @GeneratedValue public Long getId() {return id;} public void setId(Long id) {this.id = id;} @NotNull public String getName() {return name;} public void setName(String name) {this.name = name;} }
@Entity
denotes that this is an EJB entity bean
@Name
denotes that this is a Seam component
@Id
denotes that this is the identification parameter for this entity (the primary key)
@GeneratedValue
denotes that this value is auto-generated
@NotNull
is a validation check, which states that this value can not be null (there are tricks to validation, keep reading)
-
Create the SavePersonAction stateless session bean (SLSB)
package org.domain.myProject.session; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.domain.myProject.entity.Person; import org.domain.myProject.session.local.SavePersonLocal; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Out; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.log.Log; @Stateless @Name("savePerson") public class SavePersonAction implements SavePersonLocal { @Logger private Log log; @In FacesMessages facesMessages; @PersistenceContext private EntityManager em; /* We have an '@Out' on this to push back person (after any modifications we make * in our bean here) onto the page. Since we're nullifying person in our save method, * we need to state that it's not required to have any value. */ @In @Out(required=false) private Person person; public void save() { //Simple persisting with the EntityManager em.persist(person); //Note that the id of our person was generated and populated. facesMessages.add("Person was saved with an id of "+ person.getId()); log.info(person.getName() +" was saved."); //We're nullifying person so that the page is blank after saving. person = null; } }
@Stateless
makes this class a stateless session bean
@Logger
this causes Seam to bring up our logger, a nice short-cut to having to declare one ourselves
@In
this has Seam inject (or pull) the component that has this annotation. In the case of "facesMessages", Seam is going to bring in that specific component for our use. That particular component is declared as part of the initial set-up.
@Out
this has Seam outject (or push) the component that has this annotation.
@PersistenceContext
this tells Seam to use the below EntityManager as our persistence context for handling entities
The rest of this should be adequately explained by the in-class comments
-
Create the EditPersonForm stateful session bean (SFSB)
package org.domain.myProject.session; import static javax.persistence.PersistenceContextType.EXTENDED; import java.util.List; import javax.ejb.Remove; import javax.ejb.Stateful; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.domain.myProject.entity.Person; import org.domain.myProject.session.local.EditPersonLocal; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Begin; import org.jboss.seam.annotations.Destroy; import org.jboss.seam.annotations.End; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.datamodel.DataModel; import org.jboss.seam.annotations.datamodel.DataModelSelection; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.log.Log; @Stateful @Name("editPerson") @Scope(ScopeType.CONVERSATION) public class EditPersonForm implements EditPersonLocal { @Logger private Log log; @In FacesMessages facesMessages; @PersistenceContext(type = EXTENDED) private EntityManager em; @DataModel private List<Person> people; @DataModelSelection @Out(required=false) private Person person; @SuppressWarnings("unchecked") @Factory("people") @Begin public void findPeople(){ log.info("Finding the people"); if (people == null) log.info("people is null"); else if (people.size() == 0) log.info("people has a size of zero"); else log.info("Strange, because people has " +people.size() + " entries"); people = em.createQuery("from Person u").getResultList(); } public void remove(){ log.info("Deleting " +person.getName()); em.remove(person); people.remove(person); } @End public void clear(){ //We're clearing person since it is being outjected and would otherwise populate the entry page. person = null; } public void update() { log.info("Updating " + person.getId()); em.persist(person); } @Destroy @Remove public void destroy() {} }
Here, we're using the
CONVERSATION
scope, which means that Seam will keep this bean in scope from the time we
@Begin
our "conversation", until the time that we
@End
it. This can span multiple page, but for now we're just having it on the one.
We've given our PersistenceContext the parameter of "
(type = EXTENDED)
" so that it will hang around longer then just one method
@DataModel
tells Seam that the below list is going to be used as a data model (needed for our JSF, as you'll see)
@DataModelSelection
tells Seam what we'll be selecting from the data model. Needed for when we pull stuff back in for useage
@Factory
when the component inside the factory (in this case, "people") is null, Seam will call this method. As can be seen, we're using it to generate our data model.
The
destroy()
method at the bottom is needed by Seam to dispose of this SFSB. Just note that all SFSB's have to have this.
-
Create the person.xhtml page
<!DOCTYPE composition 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:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"> <f:view> <h:form> <s:validateAll> Name: <h:inputText value="#{person.name}" required="true" requiredMessage="You need to enter a name"></h:inputText> <br /> <h:commandButton value="Save" action="#{savePerson.save()}" ></h:commandButton> <s:button value ="List" view="/listPeople.xhtml"></s:button> </s:validateAll> </h:form> <h:messages ></h:messages> </f:view> </html>
All JSF components have to go inside the
<f:view>
tag
<s:validateAll>
tells Seam to run validation on all fields within this tag
Please note that there's a snag with JSF as of this writing: it does not run any validation against fields the user has left blank.
<h:inputText>
is how we input values. The
"#{person.name}"
is an example of expression language (EL) which we can use to call our Seam components, their parameters and their methods
We have
required="true"
on this because of the no-validation-on-blanks mentioned above. Also keep in mind that the JSF page doesn't pass "null" on blanks, but instead an empty String.
<s:button>
differs from
<h:commandButton>
primarily in that the
<s:button>
doesn't submit the form, it just calls an action and goes to a location. We could have put this outside of the
<h:form>
and been fine.
<h:messages ></h:messages>
displays any messages that we've put into the
facesMessages
component
-
Create the ~listPeople.xhtml page
<!DOCTYPE composition 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:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"> <f:view> <h:messages ></h:messages> <h:form> <h:dataTable value="#{people}" var="person"> <h:column> <f:facet name="header"> <h:outputText value="Id" ></h:outputText> </f:facet> <h:outputText value="#{person.id}" ></h:outputText> </h:column> <h:column> <f:facet name="header"> <h:outputText value="Name" ></h:outputText> </f:facet> <h:inputText value="#{person.name}" ></h:inputText> </h:column> <h:column> <f:facet name="header"> <h:outputText value="Actions" ></h:outputText> </f:facet> <h:commandButton action="#{editPerson.update()}" value="H. Update" ></h:commandButton> <h:commandButton action="#{editPerson.remove()}" value="H. Delete" ></h:commandButton> </h:column> </h:dataTable> </h:form> <s:button action="#{editPerson.clear()}" view="/person.xhtml" value="S. Return"></s:button> </f:view> </html>
Here we are using the data model with
people
(our list) as the source. Each entry in the table is referred to as "person"
The data table has columns (
<h:column>
) and each column has variace facets (attributes). In this case, we're setting the "header" facet for each column with the column name
-
And that's basically it. See the attached files for the complete project. (Do not use the big zip file, it didn't upload correctly)
The attached files show "user" instead of "person" (it's an old version), but I don't have permission to edit/remove the files. You can change it yourself, or use them only as reference.
Comments