October 17, 2007

Image Handling in Seam Apps part V: Restricting Access

This is the fifth part of a tutorial about how to manage images in your Seam based web application. This tutorial consists of several parts:

Part One: The Database Model
Part Two: File Upload
Part Three: Scaling
Part Four: Displaying and Caching
Part Five: Restricting Access

This fifth installment shows you how to use Seam simplified security mode to restrict access to certain pages and actions. This simple yet powerful mode supports authentication and role based authorization.

If you use seam-gen to generate your projects then most of the work is done for you. Writing an authentication method and defining the roles are the only things you are left with. One way of defining the roles is to have database tables that represent members and roles in your application. The tutorial found here shows you how to define Member and MemberRole classes to serve this purpose.

Authentication

In the login page you need to have an inputText tag with a value binding set to identity.username, an inputSecret tag with a value binding set to identity.password and a commandButton tag with an action set to identity.login.


<h:form id="login-form">
<span class="error"><h:messages globalOnly="true"/></span>
<s:validateAll>
<fieldset>
<legend>Enter your login details</legend>
<div>
<h:outputLabel for="membername" value="Display name"/>
<h:inputText styleClass="input-field" id="membername"
value="#{identity.username}" size="40" maxlength="40"
required="true"/>
<h:message styleClass="error" for="membername"/>
</div>
<div>
<h:outputLabel for="password" value="Password"/>
<h:inputSecret styleClass="input-field" id="password"
value="#{identity.password}" size="40" maxlength="40"
required="true"/>
<h:message styleClass="error" for="password"/>
</div>
</fieldset>

<div class="submit-buttons">
<h:commandButton value="Login" action="#{identity.login}"
id="registerButton"/>
</div>
</s:validateAll>
</h:form>

The identity.login method propagates the credentials to the method specified in the security:identity tag defined in components.xml:

<security:identity authenticate-method="#{authenticator.authenticate}" />

The authenticate method validates the credentials and then retrieves a list of roles that member has. These roles are then added to the identity component.

@Name("authenticator")
public class Authenticator {
@Logger
Log log;

@In
Identity identity;

@In(create = true, value = "entityManager")
private EntityManager em;

public boolean authenticate() {
log.info("authenticating #0", identity.getUsername());
String name = identity.getUsername();
if (name == null || name.trim().length() == 0)
return false;

String password = identity.getPassword();
String hash = null;
if (password != null && password.trim().length() != 0)
hash = DigestUtils.shaHex(identity.getPassword());
else
return false;
String query = "from gs_member m "
+ "WHERE m.memberName = :name AND m.passwordHash = :hash";
Object result = null;
try {
result = em.createQuery(query).setParameter("name", name)
.setParameter("hash", hash).getSingleResult();
} catch (NoResultException e) {
log.info("No member was found with name[#0]", name);
FacesMessages.instance().add("Could not find a member "
+ "with the given name and password.");
return false;
}
Member member = (Member) result;
if (member.getStatus().equals(MemberStatus.INACTIVE.getStatus())) {
FacesMessages.instance().add("Sorry but you need "
+ "to activate your account first. Please "
+ "follow the instructions in the email "
+ "you received at the time of registration.");
return false;
} else {
if (member.getRoles() != null) {
for (MemberRole role : member.getRoles())
identity.addRole(role.getName());
}
return true;
}
}
}

Authorization

The @Restrict annotation can be used to restrict access to methods based on a role. The #{s:hasRole('gsadmin')} expression delegates the call to identity.hasRole method passing it the values of gsadmin.

public class UploadService implements Upload {
@Restrict("#{s:hasRole('gsadmin')}")
public void upload() {
// process file upload
}
}

similarly you can define access restriction to pages in pages.xml

<page view-id="/upload.xhtml" login-required="true">
<restrict>#{s:hasRole('gsadmin')}</restrict>
</page>

The restrict tag has the same effect as the @Restrict annotation except that it applies to that specific page. An AuthorizationException is thrown in case a member who does not belong to the gsadmin role accesses the upload page. You can customize exception handling in pages.xml to perform a redirection with a custom error message.

As the name implies, the login-required indicates that only logged-in members are granted access to that specific page. If not yet logged-in, a redirection to the login page is performed and all page parameters are captured. After authenticating the member, another redirection takes place to show the original paeg. This is done with the help of two tags defined in components.xml:

<event type="org.jboss.seam.notLoggedIn">
<action expression="#{redirect.captureCurrentView}" />
</event>
<event type="org.jboss.seam.postAuthenticate">
<action expression="#{redirect.returnToCapturedView}" />
</event>


Calling Identity in Code

You can even apply access restriction inline by directly calling methods of the Identity interface. The the following example calls Identity.isLoggedIn to make sure that only logged-in members are allowed to view the artwroks:

public class ArtworkServlet extends javax.servlet.http.HttpServlet {

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

if(Identity.instance() == null || !Identity.instance().isLoggedIn()){
// show error message or redirect to home page
}

// code to view the artwork
}
}

I strongly recommend reading chapter 13 of Seam Documentation to learn more about the features and options provided to support Security in Seam.

October 16, 2007

Image Handling in Seam Apps part IV: Displaying and Caching

This is the fourth part of a tutorial about how to manage images in your Seam based web application. This tutorial consists of several parts:

Part One: The Database Model
Part Two: File Upload
Part Three: Scaling
Part Four: Displaying and Caching
Part Five: Restricting Access

This fourth installment shows you how to use a Servlet to access seam components, load data from the database and view artworks in a web browser.

Seam Context Filter

Seam 2 provides a context filter that allows servlets to access component managed by Seam. This includes built in components, such as Identity, and user defined ones. This context filter needs to be configured in components.xml with a url pattern general enough to address all servlets not processed through the JSF lifecycle.


<web:context-filter url-pattern="/gallery/*" />

You define your servlets in web.xml using the usual tags servlet and servlet-mapping:

<servlet>
<servlet-name>Artwork Servlet</servlet-name>
<servlet-class>com.truerunes.web.ArtworkServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Artwork Servlet</servlet-name>
<url-pattern>/gallery/artworks/*</url-pattern>
</servlet-mapping>

The Artwork Servlet

This servlet services all requests to view artworks whether being initiated using an anchor tag, a h:graphicImage tag or accessed directly from the web browser. The example used in this tutorial has a request uri similar to this one: /truerunes/gallery/artworks/01.jpg. The first thing you need to do is to get the file name using the getPathInfo method of the request object:

public class ArtworkServlet extends javax.servlet.http.HttpServlet {

public static final long serialVersionUID = 596009789004L;

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String pathInfo = request.getPathInfo();
String fileName = WebUtil.getFileName(pathInfo);
if (fileName == null) {
String contextPath = request.getContextPath();
response.sendRedirect(contextPath + "/404.jsf");
return;
}
}
}

public class WebUtil {

public static String getFileName(String path) {
// Path format is "/file_name.jpg"
if (path != null && path.length() > 1) {
String fileName = path.substring(1);
int dotIndex = fileName.indexOf('.');
String extention = fileName.substring(dotIndex + 1);
if (extention.equals(Extention.jpg.getExtention())
|| extention.equals(Extention.png.getExtention())
|| extention.equals(Extention.gif.getExtention())) {
return fileName;
}
}
return null;
}

enum Extention {
jpg("jpg"), png("png"), gif("gif");

Extention(String extention) {
this.extention = extention;
}

private String extention;

String getExtention() {
return this.extention;
}
}
}

Now comes the beauty of Seam Context Filter, getting a component is as simple as calling Component.getInstance() with the name of the component you need. Here an entity manager and a user transaction are obtained using the getInstance method. Since an user managed transaction is obtaine you need to demarcate the boundaries of the transaction and account for any exceptions. The status of the transaction is verified agains Status.STATUS_ACTIVE to make sure that the transaction has not been started and the transaction manager did not begin the two-phase commit.

public class ArtworkServlet extends javax.servlet.http.HttpServlet {

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

// Thanks to Seam Context Filter, getting an entity manager is easy.
EntityManager em = (EntityManager) Component
.getInstance("entityManager");
UserTransaction utx = null;
boolean txStarted = false;
ArtworkInfo artworkInfo = null;
try {
utx = (UserTransaction) Component
.getInstance("org.jboss.seam.transaction.transaction");
if (utx.getStatus() != Status.STATUS_ACTIVE) {
utx.begin();
txStarted = true;
}
em.joinTransaction();
String query = "from gs_artwork_info a where a.fileName = :filename";
artworkInfo = (ArtworkInfo) em.createQuery(query).setParameter("filename",
fileName).getSingleResult();
if (txStarted)
utx.commit();
} catch (NoResultException nre) {
err.println("No artwork was found with name " + fileName);
} catch (Exception e) {
err.println("Exception when trying to read artwork " + fileName);
err.println("Exception message: " + e.getMessage());
e.printStackTrace();
try {
if (txStarted)
utx.rollback();
} catch (Exception ex) {
err.println("Rollback attempt failed: "+ex.getMessage());
ex.printStackTrace();
}
}
// calling em.close() causes "Exception calling @Destroy method."
}
}

Before streaming the artwork to the web browser, two important HTTP headers need to be set; Expires and Last-Modified. With these two header specified artworks will be cached locally in the web browser cache. This is crucial to avoid having to serve the same artwork each time a user decides to revisit a page that has an artwork. The Expires header is set to be a year from now and the Last-Modified header is set to the date the artwork got uploaded to the server.

public class ArtworkServlet extends javax.servlet.http.HttpServlet {

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
if(artworkInfo == null){
err.println("Could not find an artwork with file name: "+fileName);
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/404.jsf");
return;
}

Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.YEAR, 1);
response.setDateHeader("Expires", calendar.getTimeInMillis());

calendar.setTime(artworkInfo.getUploadDate());
response.setDateHeader("Last-Modified", calendar.getTimeInMillis());

response.setContentType(artworkInfo.getContentType());
Artwork artwork = artworkInfo.getArtwork();
response.setContentLength((int)artwork.getSize());
response.getOutputStream().write(artwork.getData());
response.getOutputStream().flush();
response.getOutputStream().close();
}
}

October 15, 2007

Image Handling in Seam Apps Part III: Scaling

This is the third part of a tutorial about how to manage images in your Seam based web application. This tutorial consists of several parts:

Part One: The Database Model
Part Two: File Upload
Part Three: Scaling
Part Four: Displaying and Caching
Part Five: Restricting Access

This third installment shows you how to scale uploaded artworks to generate thumbnails of a certain size. The method used to scale the artwork is called the Progressive Bilinear Scaling approach and is taking from Filthy Rich Clients. With this approach the artwork is scaled iteratively toward the final size, by exactly 50 percent each time until the final iteration.

When it comes to scaling images in Java, one would call either drawImage or getScaledInstance. The drawImage method has the advantage of being fast, but as the scale magnitude increases the quality drops significantly. The scaleImage method provides reasonable quality downscales when the scale factor is greater than half the original artwork size. On the other hand, the getScaledInstance provides the best quality for large downscales, however, the method has some performance implications. With getScaledInstance the performance difference involved can easily be in orders of magnitude. With the Progressive Bilinear approach you get a decent downscaled artwork in fraction of the time required by the getScaledInstance method. I would strongly recommend reading chapter four from the Filthy Rich Clients book.

The Code

The orginal scale method discussed in the book takes accepts and returns an instance of BufferedImage. I have update the method to account for the fact that an artwork are uploaded and stored as an array of bytes. The original scaling algorithm has not been modified though.


@Stateless
@Name("uploadService")
public class UploadService implements Upload {

private byte[] data;

public void upload() {
byte[] thumbnailData = this.scale();
// see part ii of this tutorial for the complete method
}

private byte[] scale(){
InputStream inputStream = new ByteArrayInputStream(this.data);
BufferedImage uploadedImg = null;
try {
uploadedImg = ImageIO.read(inputStream);
} catch (IOException e) {
log.info("Could not craete a buffered image from the specified " +
"input stream: #0", e.getMessage());
e.printStackTrace();
}

int type = (uploadedImg.getTransparency() == Transparency.OPAQUE) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

/*
* The uploaded image has to be passed to drawImage at the first
* iteration only. For the remaining iterations, the newly scaled image
* has to be passed instead. If you find this assignment a bit confusing
* you can always assign it a null and then add an if statement similar
* to the scratchImage below.
*/
BufferedImage scaledImage = (BufferedImage) uploadedImg;
BufferedImage scratchImage = null;
Graphics2D g2 = null;

int w = uploadedImg.getWidth();
int h = uploadedImg.getHeight();
int prevW = scaledImage.getWidth();
int prevH = scaledImage.getHeight();

// the default with of the thumbnail is set to 200
int targetWidth = 200;
double ratio = (double) targetWidth / w;
int targetHeight = (int) (h * ratio);

Object hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;

do {
if (w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
if (scratchImage == null) {
// Use a single scratch buffer for all iterations
// and then copy to the final, correctly sized image
// before returning
scratchImage = new BufferedImage(w, h, type);
g2 = scratchImage.createGraphics();
}
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION , hint);
g2.drawImage(scaledImage, 0, 0, w, h, 0, 0, prevW, prevH, null);
prevW = w;
prevH = h;
scaledImage = scratchImage;
} while (w != targetWidth || h != targetHeight);
if (g2 != null) {
g2.dispose();
}
// If we used a scratch buffer that is larger than our
// target size, create an image of the right size and copy
// the results into it
if (targetWidth != scaledImage.getWidth() || targetHeight != scaledImage.getHeight()) {
scratchImage = new BufferedImage(targetWidth, targetHeight, type);
g2 = scratchImage.createGraphics();
g2.drawImage(scaledImage, 0, 0, null);
g2.dispose();
scaledImage = scratchImage;
}

String formatName = "";
if ("image/png".equals(contentType))
formatName = "png";
else if ("image/jpeg".equals(contentType))
formatName = "jpeg";

// convert the resulting image into an array of bytes
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ImageIO.write(scaledImage, formatName, outputStream);
} catch (IOException e) {
log.info("Could not write the buffered image into the specified " +
"output stream: #0", e.getMessage());
e.printStackTrace();
}
return outputStream.toByteArray();
}
}

I scaled some artworks of different sizes, 480KB up to 15.7MB, and got these results:

Size Resolution Time
482 KB (1004x669) 270 ms
567 KB (1280x1024) 1523 ms
617 KB (640x904) 180 ms
747 KB (1024x768) 284 ms
856 KB (1400x1600) 1157 ms
995 KB (1606x1700) 723 ms
1313 KB (4673x3248) 30172 ms
1432 KB (2142x3018) 2311 ms
1539 KB (2142x3017) 1576 ms
1739 KB (2517x3200) 1863 ms
1903 KB (4504x2401) 2607 ms
2002 KB (1935x2708) 1300 ms
2320 KB (2399x3577) 2798 ms
2628 KB (2853x4020) 15772 ms
5.2 MB (1568x2256) 5475 ms
11.4 MB (3508x2500) 9443 ms
15.7 MB (3508x2500) 5600 ms

October 14, 2007

Image Handling in Seam Apps Part II: File Upload

This is the second part of a tutorial about how to manage images in your Seam based web application. This tutorial consists of several parts:

Part One: The Database Model
Part Two: File Upload
Part Three: Scaling
Part Four: Displaying and Caching
Part Five: Restricting Access

This second installment shows you how to upload artworks using Seam built-in support for file upload. The uploaded artwork is then persisted in the database.

UPDATE: Thanks to Matt for pointing out the mistake I made and for suggesting the solution. The UploadService session been has been redefined as a Stateful session bean. It cannot be defined as a Stateless session bean since the EJB Container can swap an instance of a stateless session beans as soon as it finishes serving a method invocation. If you still want to use a Stateless session been then you need to pass all the information a method needs to know via parameters. You cannot assume that the same bean instance will serve all of your requests.

The User Interface

An authorized member selects a file from the local file system and sets a title for the artwork.



To upload files, Seam 2 has the fileUpload tag as part its tag library. This tag accepts a number of useful attributes which allow you to set the uploaded data in byte[] or InputStream, the file's content type, the name of the uploaded file and its size. You can also specify a list of accepted image types in case you only allow, say, PNG and JPEG images.


<h:form id="registration-form" enctype="multipart/form-data">
<span class="error"><h:messages globalOnly="true"/></span>
<s:validateAll>
<fieldset>
<legend>Enter file to upload</legend>
<div>
<h:outputLabel for="title" value="Title"/>
<h:inputText styleClass="input-field" id="title" required="true"
value="#{uploadService.title}" size="40" maxlength="40"/>
<h:message styleClass="error" for="title"/>
</div>
<div>
<h:outputLabel for="artwork" value="Artwork"/>
<s:fileUpload id="artwork" styleClass="input-field"
data="#{uploadService.data}" accept="image/*"
contentType="#{uploadService.contentType}"
fileName="#{uploadService.fileName}" fileSize="#{uploadService.size}"
size="40" maxlength="40" required="true"/>
<h:message styleClass="error" for="artwork"/>
</div>
</fieldset>

<div class="submit-buttons">
<h:commandButton value="Upload"
action="#{uploadService.upload}" id="registerButton"/>
</div>
</s:validateAll>
</h:form>

To handle multipart requests, you need to set the encoding type of the html form to multipart/form-data and to configure Seam Multipart Servlet Filter in web.xml.

<filter>
<filter-name>Seam Filter</filter-name>
<filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Seam Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

The Multipart Filter has two properties that can be set in components.xml: the first one offerers the option of storing the uploaded data in a temporary file rather than main memory while the second one offers setting a maximum size of uploads. It is recommended that these properties be set to avoid very large uploads and to free the memory resources.

<component class="org.jboss.seam.web.MultipartFilter">
<property name="createTempFiles">true</property>
<property name="maxRequestSize">4194304</property>
</component>


The Upload Service

Persisting the uploaded artwork is taking care in the UploadService stateless session bean. It has properties that can hold the values of the attributes specified in the fileUpload tag and and upload method that persists the artwork.

@Local
public interface Upload {
byte[] getData();
void setData(byte[] data);

String getContentType();
void setContentType(String contentType);

String getFileName();
void setFileName(String fileName);

String getTitle();
void setTitle(String title);

int getSize();
void setSize(int size);

void upload();
}

The first thing the upload method does is to get a scaled version of the artwork, scaling to be discussed in part three. It then gets an instance of the member who uploaded the artwork. After that the upload method creates instances of Thumbnail, Artwork and ArtworkInfo and call the persist method on each one of them.

@Stateful
@Name("uploadService")
public class UploadService implements Upload {

@Logger Log log;

@In(create = true, value = "entityManager")
private EntityManager em;

private byte[] data;
private String contentType;
private String fileName;
private String title;
private int size;

// setters/getters omitted

@Restrict("#{s:hasRole('gsadmin')}")
public void upload() {
byte[] thumbnailData = this.scale();

// Get the member early to avoid updating the info instance later.
Member uploader = (Member) em.createQuery(
"from gs_member m where m.memberName = :name").setParameter(
"name", Identity.instance().getUsername()).getSingleResult();

Thumbnail thumbnail = new Thumbnail();
thumbnail.setData(thumbnailData);
thumbnail.setSize(thumbnailData.length);
em.persist(thumbnail);

Artwork artwork = new Artwork();
artwork.setData(data);
artwork.setSize(size);
em.persist(artwork);

ArtworkInfo info = new ArtworkInfo();
info.setArtwork(artwork);
info.setThumbnail(thumbnail);
info.setContentType(contentType);
info.setFileName(fileName);
info.setHitCount(0);
info.setTitle(title);
info.setUploadDate(new Date());
info.setUploader(uploader);
em.persist(info);
}

@Remove
public void destroy(){}
}

Notice the call to the setUploader method, this call is required whenever you add or remove Member's ArtworkInfos. The relationship will not change in the database if you do not make this call. Also, to avoid executing extra database queries the uploader property is set before persisting the artwork info object.

Usually on production database servers there is a limit on the max packet size permitted, on MySQL the limit is 1MB. Keeping the limit would cause an exception to be thrown when an artwork greater than 1MB gets uploaded. To check for this value under Linux, you can run the following command:

shell> mysqladmin -u root -p variables | grep max_allowed_packet

Or the following command once you login into MySQL:

mysql> show variables like 'max%' ;

All you need to do is to set this value to a higher number. Under openSUSE 10.3 you need to change the MySQL configurations found in /etc/my.cnf, to be more specific you need to change line 31.

You can read more about the packet size limit here: http://dev.mysql.com/doc/refman/5.0/en/packet-too-large.html

Image Handling in Seam Apps Part I: DB Model

This is the start of a tutorial about how to manage images in your Seam based web application. This tutorial consists of several parts:

Part One: The Database Model
Part Two: File Upload
Part Three: Scaling
Part Four: Displaying and Caching
Part Five: Restricting Access

Introducing the example

The example used to demonstrate this tutorial allows a member to upload artworks of various sizes. The member has to login first and be a member of a predefined group. Once uploaded a thumbnail of the original artwork is generated on the fly. The original artwork and the generated image are then stored in the database. Other members can then view both of the thumbnail and the original artwork.

Database Schema

This first part of our tutorial introduces you the database schema and the entity beans representing the tables. To store the artwork and its thumbnail you need an additional table to store information shared between the two. This tables is called artwork_info and it has a one to one relationship with the other two tables. It also has a many to one relationship with the member table.



The following desc commands show the meta-data for them.



The data column in the gs_artwork table is defined as MEDIUMBLOB since a normal BLOB type can hold only up to 65,535 byte which is almost 64KB. You can store almost 16MB using a MEDIUMBLOB.

Programming Model

Each database table maps to its own Java class. There is a One-To-One Unidirectional relationship between the ArtworkInfo class and each of Artwork and Thumbnail. The Member class has a One-To-Many Bidirectional relationship with the ArtworkInfo class.



Let take a look at how we would mark up the ArtworkInfo class to implement the One-To-One relationship:


@Entity(name = "gs_artwork_info")
@Name("artworkInfo")
public class ArtworkInfo implements Serializable {

public static final long serialVersionUID = 596009789005L;

@Id
@GeneratedValue(strategy = AUTO)
@Column(name = "artwork_info_id")
private int id;

@NotNull
@Column(name = "file_name", unique = true)
private String fileName;

@NotNull
@Column(name = "content_type")
private String contentType;

@NotNull
@Column(name = "upload_date")
private Date uploadDate;

@NotNull
private String title;

@NotNull
@Column(name = "hit_count")
private int hitCount;

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "artwork_id")
private Artwork artwork;

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "thumbnail_id")
private Thumbnail thumbnail;

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "uploader")
private Member uploader;

// setters/getters omitted
}


The One-To-One relationship is defined using the @OneToOne annotation and is mapped using the @JoinColumn annotation. The @JoinColumn annotation defines the column in the ArtworkInfo's table that maps to join column, primary key in this case, of the Artwork's table. The @OneToOne annotation specifies the fetch type to be LAZY to avoid eagerly loading the artwork instance from the database. If you don't specify this property, your persistence provider would simply run a second query to retrieve all the columns of the Artwork's table when an instance of ArtworkInfo is read from the database.

To define the One-To-Many relationship between Member and ArtworkInfo you need to have an instance of Member annotated with @ManyToOne in the ArtworkInfo class. To access all the artworks uploaded by a member you need to define a collection-based relation property, Set<ArtworkInfo> in this case, with the @OneToMany annotation. The mappedBy property tells the persistence manager that the information for mapping this relationship to our tables is specified in the ArtworkInfo class, specifically to the uploader property of ArtworkInfo.


@Entity(name = "gs_member")
@Name("member")
public class Member implements Serializable {

public static final long serialVersionUID = 596009789001L;

@Id
@GeneratedValue(strategy = AUTO)
@Column(name = "member_id")
private Integer id;

@NotNull
@Length(max = 30, min = 2)
@Column(name = "member_name", unique = true)
private String memberName;

@OneToMany(mappedBy = "uploader", fetch = FetchType.LAZY)
private Set<ArtworkInfo> artworks;

// setters/getters omitted
}


The Artwork and Thumbnail classes are almost identical with minimal difference between the two. Since the data property stores the actual artwork in binary format you need to have the @Lob annotation. It informs the persistence manager that this property requires a lot of memory and that it needs to handle it internally as Blob. Here even though the data property is specified to be Lazily fetched, the persistence manager simply ignores this property; I have tried to use java.sql.Blob but even in this case the hint of lazily fetching the property was ignored.


@Entity(name = "gs_artwork")
@Name("artwork")
public class Artwork implements Serializable {

public static final long serialVersionUID = 596009789003L;

@Id
@GeneratedValue(strategy = AUTO)
@Column(name = "artwork_id")
private int id;

// the persistence manager ignores the fetch property.
@Lob
@Basic(fetch=FetchType.LAZY)
private byte[] data;

// setters/getters omitted
}

October 4, 2007

Seam Page Parameter with cutom Validators and Converters

I recently wrote about User Friendly Paging with Seam in which I used the @RequestParameter annotation. However, Gavin King has suggest that Seam page parameter be used rather than @RequestParameter. One advantage of using page parameter is that Seam set the values after performing appropriate type conversion. More important, "the <param> declaration is bidirectional." I would suggest reading Seam Documentation for more information.

The Components

Now that page parameters to be used, there is no need for the @RequestParameter and @out annotations, however, you need to define setter methods instead.


@Local
public interface MemberListing {
void setMemberList();
void setStatus(String status);
void setPageNumber(Integer pn);
void setPageSize(Integer ps);
}
@Stateless
@Name("memberListingService")
public class MemberListingService implements MemberListing{

private String status;
private String pn = null;
private String ps = null;

public void setStatus(String st){
status = st;
}

public void setPageNumber(Integer pageNumber){
pn = pageNumber.toString();
}

public void setPageSize(Integer pageSize){
ps = pageSize.toString();
}
}


Declaring the Page Parameters

For each page parameter, you need to declare a param tag with name and value attributes. Optionally, you can specify converter and validator as well.


<page view-id="/members.xhtml" action="#{memberListingService.setMemberList}">
<param name="st" value="#{memberListingService.status}"/>
<param name="pn" value="#{memberListingService.pageNumber}"
validator="#{pageNumberValidator}"/>
<param name="ps" value="#{memberListingService.pageSize}"
converter="#{pageSizeConverter}"/>
</page>


Custom Validator and Converter

Seams provides the @Validator and @Converter annotations to define components as custom JSF validators and converters. What is amazing about Seam is that you do not need to register these custom validators and converters with JSF. Seam takes care of that for you :)

The reason I am using these components here is because I want to display a custom error message for each parameter. If the standard converters/validators were used then the same error message would be displayed for parameters with same type.


@Name("pageNumberValidator")
@BypassInterceptors
@Validator
public class PageNumberValidator implements javax.faces.validator.Validator {

@Logger
private Log log;
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
log.info("PageNumberValidator.validate called");
try {
Integer pageNumber = Integer.parseInt(value.toString());
if (pageNumber < 1) {
FacesMessage message = new FacesMessage();
message.setDetail("Invalid page number [less than 1]");
message.setSummary("The page number cannot be "+pageNumber);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
} catch (NumberFormatException e) {
e.printStackTrace();
FacesMessage message = new FacesMessage();
message.setDetail("Invalid or missing page number");
message.setSummary("The page number has to be a valid number");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(message);
}
}
}


The validator here is annotated with Seam @Validator annotation and at the same time implements the Validator interface from the JSF API. The name use specify in the @Name annotation is the one used in the param tag above. To have a custom error message you define an instance of FacesMessage and set its properties accordingly. Then you need to pass it as a parameter to the newly thrown instance of ValidatorException. I know you are saying, this is too much for a custom error message but this is the only way I was able to find.

It would have been much simpler if the param tag had validatorMessage and convertorMessage attributes. I don't know but it might not be possible due to limitations in the JSF API.

Here is the code for the converter:

@Name("pageSizeConverter")
@BypassInterceptors
@Converter
public class PageSizeConverter implements javax.faces.convert.Converter {

@Logger
private Log log;

public Object getAsObject(FacesContext context, UIComponent component,
String value) throws ConverterException {
log.info("PageSizeConverter.getAsObject called");
if(value == null || value.trim().length() == 0)
return null;
Integer i = null;
try {
i = new Integer(value);
} catch (NumberFormatException e) {
e.printStackTrace();
FacesMessage message = new FacesMessage();
message.setDetail("Invalid page size [String specified]");
message.setSummary("The page size has to be a valid number");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(message);
}
return i.intValue();
}

public String getAsString(FacesContext context, UIComponent component,
Object value) throws ConverterException {
log.info("PageSizeConverter.getAsString called");
return value + "";
}
}


I totally forgot the screenshots


October 2, 2007

User Friendly Paging with Seam

Paging, also known as pagination, has many different implementations on the PHP universe. Developers just pick the one they like. However, there are not that many on the Java playground. You might have heard about the Value List Handler design pattern or used the Data Scroller component from the MyFaces project. Developers might find these approaches to be a bit complex or restrictive. Personally, I have read about them but never got the chance to use them.

With Hibernate and the Java Persistence API, the game has changed and Paging is much simpler now. Back in 2004, Gavin King blogged about a pattern to do Pagination in Hibernate and EJB3. The pattern is so simple and I really like it. The only thing that is remaining now is to integrate this pattern with Seam ;)

Update: Gavin King has suggest that Seam page parameter be used rather than @RequestParameter. Take a look here for using Seam page parameters with custom validators and converters.

Enhancing the pattern

The pattern Gavin introduced allows you to display "next" and "previous" buttons to the user and only retrieve the needed query results. This is nice and handy, however, users are impatient and get frustrated from repeatedly hitting the "next" and "previous" buttons to browse through a collection of items. I used to have a "browse" page with only these two buttons and one day I received an email from a user requesting that I offer pagination similar to the one seen on phpBB powered forums.




So, I have decided to do something similar but a bit different.

The Addons


Similar to phpBB pagination, the pattern discussed next has "first", "previous", "next", "last" and a list of links that the user can directly navigate two. As a developer you can specify the number of items to display per listing.



Notice that the total number of pages is displayed and users get to know which page they are currently browsing. In case there are not that many records in the database and there are only few pages to browse, page numbers are displayed rather than the verbose words.



The Components

To achieve this you two a component. One to validate data passed in the URL and to define the database queries. The second is responsible for identifying the format of the paging and the links to show.

The first component is a stateless session bean called MemberListingService that has a single method called setMemberList:


@Local
public interface MemberListing {
void setMemberList();
}

This method perform validation on the three parameters passed in the URL and set using the @RequestParameter annotation. If required, default values will be set for parameters that are missing or have invalid values.

@Stateless
@Name("memberListingService")
public class MemberListingService implements MemberListing {

// By default page number/index is NOT zero based. The first value is ONE.
private static int FIRST_PAGE_INDEX = 1;
private static String DEFAULT_STATUS = MemberStatus.ACTIVE.getStatus();

@RequestParameter("st")
@Out(scope = EVENT, value = "st", required = false)
private String status;

@RequestParameter("pn")
@Out(scope = EVENT, value = "pn", required = false)
private String pn = null;

@RequestParameter("ps")
@Out(scope = EVENT, value = "ps", required = false)
private String ps = null;

public void setMemberList() {
Integer pageIndex, pageSize = -1;

if (status == null || status.trim().equals("")) {
FacesMessages.instance().add("No status was specified.");
status = DEFAULT_STATUS;
}

if (pn == null || pn.trim().equals("")) {
FacesMessages.instance().add("No page number was specified.");
pageIndex = FIRST_PAGE_INDEX;
} else {
try {
pageIndex = Integer.parseInt(pn);
} catch (NumberFormatException e) {
FacesMessages.instance().add("invalid page number specified.");
pageIndex = FIRST_PAGE_INDEX;
}
}

if (ps == null || ps.trim().equals("")) {
FacesMessages.instance().add("No page size was specified.");
} else {
try {
pageSize = Integer.parseInt(ps);
} catch (NumberFormatException e) {
FacesMessages.instance().add("invalid page size specified.");
}
}
}
}

The next step is to create two Query objects. The first one is for the columns you want to show in you listing and the second is for getting a count of the records.

@Stateless
@Name("memberListingService")
public class MemberListingService implements MemberListing {

@In(create = true, value = "entityManager")
private EntityManager em;

@Out(scope = EVENT, value = "page")
private Page page;

public void setMemberList() {

String selectStatement = "from gs_member m " + "WHERE m.status = :status "
+ "order by m.joinDate";
Query selectQuery = em.createQuery(selectStatement).setParameter("status",
status);

String countStatement = "SELECT COUNT(m) " + "FROM gs_member m "
+ "WHERE m.status = :status ";

Query countQuery = em.createQuery(countStatement).setParameter("status",
status);

this.page = new Page(selectQuery, countQuery, pageIndex, pageSize);
}
}

These two Query objects are then passed to a new instance of the Page class. If you execute the select query as is, you will retrieve all the records from the database. In the constructor of the Page class, further operations are applied to limit the list of records you get. The count query on the other hand will be executed as is without any further modifications.

public class Page {

private static int DEFAULT_PAGE_SIZE = 8;
private static int DEFAULT_STEP_SIZE = 2;
private static int MIN_PAGE_SIZE = 2;
private static int MAX_PAGE_SIZE = 16;

private List results;
private int pageIndex;
private int pageSize;
private int stepSize;
private int lastPageIndex;
private int leftSteps;
private int rightSteps;
private boolean leftDots;
private boolean rightDots;
private boolean simplePaging;

@In(create = true, value = "entityManager")
private EntityManager em;

public Page(Query selectQuery, Query countQuery, int index, int size) {
if (index <= 0)
index = 1;

// If page index is not zero based
pageIndex = index;

// Make sure that the page size is within its limits
if (size < MIN_PAGE_SIZE || size > MAX_PAGE_SIZE)
pageSize = DEFAULT_PAGE_SIZE;
else
pageSize = size;

// You might consider having a min and max as boundaries
stepSize = DEFAULT_STEP_SIZE;

// Get the total number of items in the database.
// If count was of type Long you would lose the precision when
// calling Math.ceil() --> count is declared as double
double count = (Long) countQuery.getSingleResult();
if (count <= 0) {
// You need to handle this situation. One option is to throw an
// exception or display an error message using FacesMessages.
return;
}

// calculate the number of pages and set last page index
lastPageIndex = (int) Math.ceil(count / pageSize);

// make sure that the page index in not out of scope
if (pageIndex > lastPageIndex)
pageIndex = 1;

// calculate the minimum number of paging items to show
int minPagination = 3 + 2 * stepSize;
if (lastPageIndex <= minPagination) {
leftDots = rightDots = false;
simplePaging = true;
} else {
simplePaging = false;
setLeftDots();
setRightDots();
setLeftSteps();
setRightSteps();
}

results = selectQuery.setMaxResults(pageSize).setFirstResult(
pageSize * (pageIndex - 1)).getResultList();
}

private void setLeftDots() {
// current page - (step size + 1) > 1
// current page - step size > 1 + 1
// ex, current page = 5 and step size = 2
// output 1 ... 3 4 5
// 5 - 2 > 2 --> 3 > 2 --> true
leftDots = pageIndex > (stepSize + 2);
}

private void setRightDots() {
// current page + (step size + 1) < last page
// current page + step size < last page - 1
// ex, current page = 25, step size = 2 and last page = 29
// output 25 26 27 ... 29
// 25 + 2 < 29 - 1 --> 27 < 28 --> true
rightDots = (pageIndex + stepSize) < (lastPageIndex - 1);
}

private void setLeftSteps() {
if (leftDots)
leftSteps = stepSize;
else {
// count = current page - (first + 1)
// ex, first = 1 and page = 4
// count = 4 - (1 + 1) = 2
// output 1 2 3 4
leftSteps = pageIndex - 2;
}
}

private void setRightSteps() {
if (rightDots)
rightSteps = stepSize;
else {
// count = last page - ( current page + 1)
// ex, last = 29 and current = 26
// count = 29 - 27 = 2
// output 26 27 28 29
rightSteps = lastPageIndex - pageIndex - 1;
}
}

public boolean isLeftDots() {
return leftDots;
}

public boolean isRightDots() {
return rightDots;
}

public int getLeftSteps() {
return leftSteps;
}

public int getRightSteps() {
return rightSteps;
}

public List getResults() {
return results;
}

public int getPageIndex() {
return pageIndex;
}

public int getLastPageIndex() {
return lastPageIndex;
}

public boolean showPageIndex() {
return pageIndex != 1 && pageIndex != lastPageIndex;
}

public boolean isSimplePaging() {
return simplePaging;
}
}

I have added comments to the code since it's simpler to explain it that way, otherwise, I would end up with a long paragraph. Feel free to ask a question or a comment if you need further explanation. Note that certain links are displayed based on the current page






The members.xhtml page makes use of the "page" component outjected at the call to setMemberList is done. There is not much to explain here also so you just go through it and ask any question if something is not clear.

<?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:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core">

<ui:composition template="layout/main_template.xhtml">
<ui:define name="title">True Runes Member Listing</ui:define>
<ui:define name="content">
<!-- cellspacing is used to remove the default padding between cells -->
<h:dataTable cellspacing="0" id="memberListTable" value="#{page.results}"
var="mem" rowClasses="odd, even">
<caption>Members List</caption>
<h:column col="memberGroup">
<f:facet name="header">Member Name</f:facet>
<h:outputText value="#{mem.memberName}"/>
</h:column>
<h:column>
<f:facet name="header">Joined on</f:facet>
<h:outputText value="#{mem.joinDate}">
<f:convertDateTime type="date"/>
</h:outputText>
</h:column>
<h:column styleClass="last">
<f:facet name="header">Status</f:facet>
<h:outputText value="#{mem.status}"/>
</h:column>
</h:dataTable>

<c:choose>
<c:when test="#{page.simplePaging}">
<div id="paging">
<h:outputText value="Page #{page.pageIndex} of #{page.lastPageIndex} pages "
rendered="#{page.lastPageIndex > 1}"/>
<c:forEach var="i" begin="1" end="#{page.lastPageIndex}">
<h:outputLink value="members.seam">
<h:outputText value="#{i}"/>
<f:param name="st" value='#{st != null ? st : ""}'/>
<f:param name="ps" value='#{ps != null ? ps : 0}'/>
<f:param name="pn" value="#{i}" />
</h:outputLink>
</c:forEach>
</div>
</c:when>

<c:otherwise>
<div id="paging">
<h:outputText value="Page #{page.pageIndex} of #{page.lastPageIndex} pages "
rendered="#{page.lastPageIndex > 1}"/>

<h:outputLink value="members.seam" rendered="#{page.pageIndex != 1}">
<h:outputText value="First"/>
<f:param name="st" value='#{st != null ? st : ""}'/>
<f:param name="ps" value='#{ps != null ? ps : 0}'/>
<f:param name="pn" value="1" />
</h:outputLink>

<h:outputText value=" 1 " rendered="#{page.pageIndex == 1}"/>

<h:outputLink value="members.seam" rendered="#{page.pageIndex != 1}">
<h:outputText value="Previous"/>
<f:param name="st" value='#{st != null ? st : ""}'/>
<f:param name="ps" value='#{ps != null ? ps : 0}'/>
<f:param name="pn" value="#{page.pageIndex - 1}" />
</h:outputLink>

<h:outputText value=" ... " rendered="#{page.leftDots}"/>

<c:forEach var="i" begin="#{page.pageIndex - page.leftSteps}"
end="#{page.pageIndex - 1}">
<h:outputLink value="members.seam">
<h:outputText value="#{i}"/>
<f:param name="st" value='#{st != null ? st : ""}'/>
<f:param name="ps" value='#{ps != null ? ps : 0}'/>
<f:param name="pn" value="#{i}" />
</h:outputLink>
</c:forEach>

<h:outputText value=" #{page.pageIndex} "
rendered="#{page.showPageIndex()}"/>

<c:forEach var="i" begin="#{page.pageIndex + 1}"
end="#{page.pageIndex + page.rightSteps}">
<h:outputLink value="members.seam">
<h:outputText value="#{i}"/>
<f:param name="st" value='#{st != null ? st : ""}'/>
<f:param name="ps" value='#{ps != null ? ps : 0}'/>
<f:param name="pn" value="#{i}" />
</h:outputLink>
</c:forEach>

<h:outputText value=" ... " rendered="#{page.rightDots}"/>

<h:outputLink value="members.seam"
rendered="#{page.pageIndex != page.lastPageIndex}">
<h:outputText value="Next"/>
<f:param name="st" value='#{st != null ? st : ""}'/>
<f:param name="ps" value='#{ps != null ? ps : 0}'/>
<f:param name="pn" value="#{page.pageIndex + 1}" />
</h:outputLink>

<h:outputLink value="members.seam"
rendered="#{page.pageIndex != page.lastPageIndex}">
<h:outputText value="Last"/>
<f:param name="st" value='#{st != null ? st : ""}'/>
<f:param name="ps" value='#{ps != null ? ps : 0}'/>
<f:param name="pn" value="#{page.lastPageIndex}" />
</h:outputLink>

<h:outputText value=" #{page.lastPageIndex}"
rendered="#{page.pageIndex == page.lastPageIndex}"/>
</div>
</c:otherwise>
</c:choose>
</ui:define>
</ui:composition>
</html>

The last part of the buzzle is in pages.xml, you need to define setMemberList as a page action for the members.xhtml page.

<page view-id="/members.xhtml" action="#{memberListingService.setMemberList}"/>

September 23, 2007

Registration with email notification using JBoss Seam

Many websites don't give users an option to review their personal information before hitting the infamous 'I agree' button to complete the registration. Everything is done using one single registration page in which the input fields serve two purposes. They are used to gather and review data at the same time. This could save you some time but at the cost of having to edit your profile later on. Sometimes you run out of luck as your name or email has a typo and you need to contact the website administrator to correct it. Not to mention the fact that many website don't let you update your user name. Also, this is not the desired behavior on auction sites, online stores and banking websites. Usually, in theses websites you have to review the transaction before you place a bid, confirm an order or perform a money transfer.

So in this tutorial you will learn how to develop a registration form that spans multiple pages. Moreover, you will learn how to send email notifications with account activation option. This is done using Seam 2 navigation rules and email templates. Let's take a look at some of the screen shots for now.

The first page has the input fields and one button. Clicking on the 'next' button triggers some validation to be performed.




Once the validation is passed the user gets redirected to the second screen which has only output components and two buttons. The 'edit' button sends you back to the first screen while the 'confirm' button completes the registration process. At this point an activation email gets send to you.



Clicking the link on the email sends you to the third screen. Here a welcome message is displayed possible redirected to the home page.



Now that you have seen the screens let take a look at the code.

The database back-end

There are two entities, Member and MemberRole, which are mapped to their own tables. There is also a third table that represents the N:M relationship between the two entities.


@Entity(name = "gs_member")
@Name("member")
public class Member implements Serializable {
public static final long serialVersionUID = 596009789001L;

@Id
@GeneratedValue(strategy = AUTO)
@Column(name = "member_id")
private Integer id;

@NotNull
@Length(max = 30, min = 2)
@Column(name = "member_name", unique = true)
private String memberName;

@NotNull
@Length(min = 40, max = 40)
@Column(name = "passwd_hash")
private String passwordHash;

@NotNull
@Length(max = 100)
@Email
@Column(unique = true)
private String email;

@NotNull
@Length(max = 15)
private String status;

@NotNull
@Column(name = "join_date")
private Date joinDate;

@ManyToMany
@JoinTable(name = "gs_member_roles",
joinColumns = @JoinColumn(name = "member_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<MemberRole> roles;
}

  • The @Entity annotation specifies that this an entity bean and the name attribute specifies the name of the database table that maps to this entity.
  • The @Name annotation defines a Seam component with the specified name.
  • The @Id annotation is used to indicate the primary key while the @GeneratedValue(strategy = AUTO) annotation instructs the database to set the primary key column using an increment each time a new record is inserted.
  • The @NotNull, @Length and @Email are part of hibernate validation.
  • In case a property has a different name than the database column it maps to, the @Column annotation can be used to specify the name of the database column and avoid the mismatch. Also, you can specify whether the property is unique or not.
  • As I mentioned earlier, there is an M:N relationship between Member and MemberRole. To define this bidirectional relationship, the @ManyToMany annotation is used. Now you need to specify the owning side of the relationship, in our case it is the Member entity so you define the @JoinTable annotation here. For this annotation to work, you need to specify few things. First you define the name of the join table using the name attribute. Second you need to define the foreign keys using the joinColumns and inverseJoinColumn attributes. The joinColumns specify the primary key of the owning entity, Member, while the inverseJoinColumns specify the primary key of the other entity, MemberRole. In both cases the primary keys are simple so only one column is specified.

Here is MemberRole.java:

@Entity(name = "gs_member_role")
@Name("memberRole")
public class MemberRole implements Serializable {
public static final long serialVersionUID = 596009789002L;

@Id
@GeneratedValue(strategy=AUTO)
@Column(name="role_id")
private Integer roleId;

@Column(name="role_name")
private String name;

@ManyToMany(mappedBy="roles")
private Collection<Member> members;

}

Note that MemberRole maintains a collection-based relationship with all its Members. The mappedBy attribute specifies the property of the Member entity bean that defines the relationship.



Tags, Logic and Rules

Now lets move on to the pages, their associated logic and the navigation rules.


<ui:composition template="layout/main_template.xhtml">
<ui:define name="title">True Runes Member Registration</ui:define>
<ui:define name="content">
<h1>Become a member</h1>
<p>Fill out the form below to become a member and start enjoying the artworks</p>
<div id="form-area">

<h:form id="registration-form">
<span class="error"><h:messages globalOnly="true"/></span> (0)
<s:validateAll>
<fieldset>
<legend>Enter your contact details</legend>
<p>
<h:outputLabel for="membername"
value="Display name"/>
<h:inputText styleClass="input-field" id="membername"
value="#{newMember.memberName}" size="40" maxlength="40"
required="true"/> (1)
<h:message styleClass="error" for="membername"/>
</p>
<p>
<h:outputLabel for="email"
value="Email address"/>
<h:inputText styleClass="input-field" id="email"
value="#{newMember.email}" size="40" maxlength="40"
required="true"> (2)

</h:inputText>
<h:message styleClass="error" for="email"/>
</p>
</fieldset>

<fieldset>
<legend>Choose a password</legend>
<div>
<h:outputLabel for="password"
value="Password"/>
<h:inputSecret styleClass="input-field" id="password"
value="#{register.password}" size="40" maxlength="40"
required="true"/> (3)
<h:message styleClass="error" for="password"/>
</div>
<div>
<h:outputLabel for="confirm"
value="Confirm password"/>
<h:inputSecret styleClass="input-field" id="confirm"
value="#{register.confirm}" size="40" maxlength="40"
required="true"/> (4)
<h:message styleClass="error" for="confirm"/>
</div>
</fieldset>


<div class="submit-buttons">
<h:commandButton value="Next"
action="#{register.verify}" id="registerButton"/> (5)
</div>
</s:validateAll>
</h:form>
</div><!-- // form-area -->
</ui:define>
</ui:composition>

(0) an h:message tag is used to show all the global messages.

(1) and (2) the display name and email are stored in a component called newMember. This component is created and outjected using a factory method of the RegistrationService bean.

(3) and (4) the password and its confirmation are stored in a component called register. The register component compares the two values and calls a method to generate a hash. This hash is then stored in the newMember component.

(5) here the command button calls the verify method of the register component which does some validations as show next.

Snipped code from RegisterService.java

@Stateful
@Name("register")
public class RegisterService implements Register {

@In(required=false) @Out
private Member newMember;

@In(create=true, value="entityManager")
private EntityManager em;

/**
* Password confirmation
*/
private String password;
private String confirm;

private boolean verified;

@Destroy @Remove
public void destroy() {}

public void verify() {
boolean validPass = validPassword();
boolean uniqueName = uniqueName();
boolean uniqueEmail = uniqueEmail();
verified = validPass && uniqueName && uniqueEmail;
}

private boolean validPassword(){
if(password != null && confirm != null && password.equals(confirm)){
newMember.setPasswordHash(DigestUtils.shaHex(password));
return true;
}
FacesMessages.instance().add("Passwords do not match");
return false;
}

private boolean uniqueName(){
try{
em.createQuery("from gs_member m where m.memberName = :name")
.setParameter("name", newMember.getMemberName())
.getSingleResult();
}catch(NoResultException e){
return true;
}
FacesMessages.instance().add("The display name you specified " +
"is already taken. Please select another one");
return false;
}

private boolean uniqueEmail(){
try{
em.createQuery("from gs_member m where m.email = :email")
.setParameter("email", newMember.getEmail())
.getSingleResult();
}catch(NoResultException e){
return true;
}
FacesMessages.instance().add("The email address you " +
"specified is already registered in the system.");
return false;
}

@Factory("newMember") @Begin
public void start() {
newMember = new Member();
}
}

  • The RegisterService class is defined as a Stateful session bean since each user needs a copy of the bean to store the information across method invocations. As you saw in register.xhtml, Seam manages the 'register' component as defined by the @Name annotation.
  • The start method is annotated with @Factory("newMember"), this means it is executed when register.xhtml references the newMember component but it has no value bound to it. After the method finishes execution the newly created component is outjected as indicated by the @Out annotation. Also, the start method is annotated with @Begin so that a new conversation is started when this method is called.
  • When the user clicks the next button on register.xhtml the verify method get called.
  • The validPassword method simply checks that the password and its confirmation are equal and then creates an SHA hash using the DigestUtil of the Apache commons codec library. I know that Seam has some classes to generate hashes but I find Apache commons easier to use.
  • The uniqueName and uniqueEmail methods query the database to make sure that the display name and the email are not used by some other member. Both of the methods gets their input from the newMember component.
  • As you have noticed, each of the three methods sets an error message in the FacesMessages component of Seam in case validation isn't passed.
  • If all the three methods return true, then the 'verified' propeerty is set to true. This property is used in pages.xml as you will see soon.

Before moving on to the next screen, it is time to see some navigation rules:

<page view-id="/register.xhtml">
<navigation from-action="#{register.verify}">
<rule if="#{not register.verified}">
<redirect view-id="/register.xhtml"/>
</rule>

<rule if="#{register.verified}">
<redirect view-id="/confirm.xhtml"/>
</rule>
</navigation>
</page>

Here we define the navigation rules for the register.xhtml page as specified by the view-id attribute. A navigation tag corresponds to a method call or hard-coded value. In our case the execution of the register.verify determines which rule to go with as it sets the verify boolean variable. You can think of a rule as a condition that need to be evaluated. The execution of the condition determines whether the body of the rule tag gets executed or not. If the first rule is satisfied then a redirection to register.xhtml is made in which error messages are displayed.




If validation is passed then then the body of the second rule get executed and a redirection to confirm.xhtml page is made. This page is so simple and there is only one thing that need to be mentioned.

<ui:composition template="layout/main_template.xhtml">
<ui:define name="title">True Runes Member Registration 2</ui:define>
<ui:define name="content">
<div id="form-area">
<h:form id="registration-form">
<span class="errors"><h:messages globalOnly="true"/></span>
<fieldset>
<legend>Your contact details</legend>
<p>
<h:outputLabel for="membername"
value="Display name"/>
<h:outputText styleClass="output-field" id="membername"
value="#{newMember.memberName}"/>
</p>
<p>
<h:outputLabel for="email"
value="Email address"/>
<h:outputText styleClass="output-field" id="email"
value="#{newMember.email}"/>
</p>
</fieldset>

<div class="submit-buttons">
<h:commandButton styleClass="edit-button" value="Edit Information"
action="edit" id="editButton"/>
<h:commandButton styleClass="conf-button" value="Confirm Registration"
action="#{register.confirm}" id="registerButton"/>
</div>
</h:form>
</div>
</ui:define>
</ui:composition>



The edit button has an action set to 'edit' rather than an EL. Seam gives you the flexibility to specify literals as form actions. This is to avoid having to call a method just to return a literal value.

<page view-id="/confirm.xhtml">
<navigation from-action="edit">
<redirect view-id="/register.xhtml"/>
</navigation>
<navigation from-action="#{register.confirm}">
<redirect view-id="/home.xhtml"/>
</navigation>
</page>

You can see that form-action attribute of the navigation tag is set to the same value as the action attribute of the edit button. This causes the body of the tag to be executed right away.

As the user gets redirected to register.xhtml the conversation id is passed as parameter in the URL, sid=1 in this case. Remember, the conversation started as a result of calling the start method which has the @Begin annotation.



Unlike register.xhtml , confirm.xhtml has two navigation actions and no nested rule tags. The second navigation action calls register.confirm described next:

public class RegisterService implements Register {

@In(required=false) @Out
private Member newMember;

@In(create=true, value="entityManager")
private EntityManager em;

@In(create=true)
private Renderer renderer;

private String activationCode;

@End
public void confirm() throws LoginException {
newMember.setJoinDate(new Date());
newMember.setRoles(new HashSet<MemberRole>());
newMember.setStatus(MemberStatus.INACTIVE.getStatus());

MemberRole userRole = (MemberRole)em.createQuery(
"from gs_member_role where role_name = 'user'")
.getSingleResult();
newMember.getRoles().add(userRole);
em.persist(newMember);
// to be discussed later
this.activationCode = TrueRunesUtil.getActivationCode(
newMember.getMemberName());
this.sendRegistrationEmail();
}

}

The first thing the confirm method does is setting the remaining properties of the newMember component. It also creates an instance of type MemberRole with the value of user and adds it to the list of roles the new member belongs to. Next the newMember component is persisted in the database.

Email Notification

Now comes the time to generate the activation code and send the registration email to the new member. Generating the activation code has been moved to a utility class as shown:

public class TrueRunesUtil {
public static String getActivationCode(String memberName){
String raw = "TrueRunes".concat(memberName);
return DigestUtils.md5Hex(raw);
}
}

Again DigestUtils of the Apache Commons codec library is used to generate an md5 hash that is used as an activation code. After getting the activation code, the confirm method calls the sendRegistrationEmail to actually send the email.

public class RegisterService implements Register {

@In(create=true)
private Renderer renderer;

private String activationCode;

public String getActivationCode(){
return this.activationCode;
}

private void sendRegistrationEmail(){
try{
this.renderer.render("WEB-INF/registration.xhtml");
FacesMessages.instance().add("Mail Sent.");
}catch(Exception e){
FacesMessages.instance().add("Email sending failed: "+e.getMessage());
e.printStackTrace();
}
}

}

The sendRegistrationEmail method uses Seam Mail support to send the registration email. It calls the render method of the renderer component to generate the email from a predefined template called registration.xhtml.

<m:message xmlns="http://www.w3.org/1999/xhtml"
xmlns:m="http://jboss.com/products/seam/mail"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<m:from name="Admin">admin@truerunes.com</m:from>
<m:to name="#{newMember.memberName}" address="#{newMember.email}"/>
<m:subject>TrueRunes Account Activation</m:subject>
<m:body>
<p>Dear #{newMember.memberName},</p>
<h:outputText value="Please click the following link to activate your account: "/>
<h:outputLink value="activate.seam">
<h:outputText value="activate my account"/>
<f:param name="member" value="#{newMember.memberName}"/>
<f:param name="code" value="#{register.activationCode}"/>
</h:outputLink>
<br/>
<p>If the link above does not work then please use the following link:</p>
<span>http://www.truerunes.com/activate.seam?member=#{newMember.memberName}&code=#{register.activationCode}</span>

<f:facet name="alternative">Dear #{newMember.memberName},
Please copy the following link and paste it into the location bar to
activate your account:
http://www.truerunes.com/activate.seam?member=#{newMember.memberName}&code=#{register.activationCode}
</f:facet>
</m:body>
</m:message>

registration.xhtml happens to be another Facelets template that uses new tags provided by Seam. The m:message tags defines the email message and encloses all other tags. The m:from tag defines the sender of the message and gets this information of the newMember component. You can have more than one m:to tag to define recipients and you can enclose it within ui:repeat to iterate through a predefined list of members. The m:body defines the content of the email and can accept custom tags as well as standard HTML tags. As you can see I have used different tags including p, br, h:outputLink, f:param and others. One thing to note here is the f:facet tag which define an alternative message in case your mail client does not support HTML based emails.




As you can see in this screenshot, a link with the activation code and member name is constructed. These two values are passed as parameters to activate.xhtml which defines h:messages as its only content:

<ui:composition template="layout/main_template.xhtml">
<ui:define name="title">True Runes Member Activation</ui:define>
<ui:define name="content">
<span class="error"><h:messages globalOnly="true"/></span>
</ui:define>
</ui:composition>
</html>

The activate logic resides in a method called activate in a Stateless session bean called ActivationService. The activate method is defined in pages.xml as a page action for activate.xhtml:

<page view-id="/activate.xhtml" action="#{activationService.activate}"/>

Here is the ActivationService class:

@Stateless
@Name("activationService")
public class ActivationService implements Activation{
@RequestParameter("code")
private String activationCode;

@RequestParameter("member")
private String name;

@In(create=true, value="entityManager")
private EntityManager em;

public void activate(){
if(activationCode != null && !activationCode.trim().equals("") &&
name != null && !name.trim().equals("")){
String code = TrueRunesUtil.getActivationCode(name);
if(code.equals(activationCode)){
this.updateMemberAccount();
FacesMessages.instance().add("Thank you #{name}, your " +
"account has been activated.");
return;
}else{
System.err.println("Invalid activation code");
}
}
FacesMessages.instance().add("Could not activate member account");
}

private void updateMemberAccount(){
String query = "UPDATE gs_member m SET m.status = :newStatus " +
"WHERE m.memberName = :name";
em.createQuery(query)
.setParameter("name", name)
.setParameter("newStatus", MemberStatus.ACTIVE.getStatus())
.executeUpdate();
}
}

The @RequestParameter is used to instruct Seam to set the values of activationCode and member from the parameters passed in the URL. The activate method does some validation, compute the hash and compare it with the one in the URL. If everything goes well, the status is updated in the database and a thanks message is shown on activate.xhtml.

For some reason Yahoo! Mail did not render the activation link and displayed plain text. Yahoo! treated the body and the alternate as attachments.




That is it, I hope you will make use of this tutorial :)