Chapter 13. File, rich rendering, and email support

This chapter covers

  • Handling file uploads
  • Creating PDF documents and charts
  • Sending emails with attachments
  • Customizing the UI with resource bundles

Many people playing their first round of golf question why anyone would want to torture themselves with such a maddening game, concluding that those who play it are simply masochistic. But anyone who has experienced the triumph of sinking the ball in the hole from the tee box, clearing a large water hazard, or just taking a great swing understands that there’s something extremely gratifying about golf once you get the hang of it. The same can be said about an application framework. There’s a lot to learn at first and it can seem overwhelming. Then things click. Your newfound ability makes the experience enjoyable and you get to do things you’ve never experienced before.

You saw in the last chapter how Seam and JSF component libraries take the pain out of using Ajax, making Ajax more accessible than ever. That’s just one example of how Seam provides features that are both rewarding to develop and rewarding for your customers and clients to use. In this chapter, you’ll learn how to do more fun and enjoyable tasks in Seam, including handling file uploads, creating PDF documents, rendering dynamic image data and charts, sending emails that include attachments, and adding themes to your application. This sampling represents the set of features that are quite often tossed out in the name of budget and time constraints. With Seam, you discover that performing these tasks is a breeze. They’re all just variations on what you have done so many times throughout this book.

Because several of the examples covered in this chapter work with raw file and image data, you’ll begin by learning how to accept file uploads and how to serve them back to the browser.

13.1. Uploading files and rendering dynamic images

How many times have you cringed at the requirement of processing an image upload and having it rendered on a page in the application? The problem isn’t that the task is impossible, but that it isn’t as straightforward as dealing with plain form data. In fact, accepting file uploads in Java has a well-founded reputation for being notoriously difficult. With Seam, it’s almost too easy. In this section, you’ll learn how to bind an upload form element to a Seam component to accept an image and persist it to the database. Then you’ll use Seam’s enhanced graphic component to turn that raw data back into a dynamically rendered image.

13.1.1. Accepting file uploads

Seam practically sacrifices itself to protect you from the nastiness of file uploads in Java, reducing the task to a simple EL value binding expression—it’s that dramatic. There are no buffers, stream reading, or multipart boundaries to worry about. All of that is handled for you transparently by the MultipartFilter and the MultipartRequest it wraps around the incoming servlet request. If you already have the SeamFilter configured, you don’t have to do anything else to enable Seam’s file upload support.

Seam’s file upload UI component

Seam provides a UI input component, <s:fileUpload>, for receiving file uploads from a JSF form. The file data is passed through an EL value binding that references a byte[] or InputStream property on a Seam component. The upload component can also capture the content type of the file, the filename, and the file size and apply that information to a Seam component along with the file data.

To demonstrate a file upload, we augment the registration form to allow members to upload a profile image, or avatar. Two properties have to be added to the Golfer entity, image and imageContentType, to capture the image data and content type, respectively. The relevant parts of the Golfer entity class are shown here:

  @Entity
  @PrimaryKeyJoinColumn(name = "MEMBER_ID")
  @Table(name = "GOLFER")
  public class Golfer extends Member {
      ...
      private byte[] image;
      private String imageContentType;

      @Column(name = "image_data")
      @Lob @Basic(fetch = FetchType.LAZY)
      public byte[] getImage() { return image; }
      public void setImage(byte[] image) { this.image = image; }

      @Column(name = "image_content_type")
      public String getImageContentType() { return imageContentType; }
      public void setImageContentType(String imageContentType) {
          this.imageContentType = imageContentType;
      }
  }

I’ve decided to accept the file data as a byte[]. The lazy-fetch strategy prevents the data from being loaded until the image data is requested, slightly reducing the memory footprint.

The only remaining step is to add the upload field to the registration form and wire it to the image and imageContentType properties on the Golfer entity. You also need to set the enctype attribute on the <h:form> component tag to multipart/form-data.[1] This setting tells the browser to send the form data using multipart data streams. Failure to make this adjustment will prevent the browser from sending the file data. An excerpt of the registration form with these changes applied is shown here:

1 See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2 for information about this setting.

  <h:form id="registerActionForm" enctype="multipart/form-data">
    ...
    <s:decorate id="imageField" template="layout/edit.xhtml">
      <ui:define name="label">Profile image / avatar</ui:define>
      <s:fileUpload id="image"
        accept="image/png,image/gif,image/jpeg"
        data="#{newGolfer.image}"
        contentType="#{newGolfer.imageContentType}"/>
    </s:decorate>
    ...
  </h:form>

You don’t have to make any changes to the RegisterAction class to accept the uploaded image and have it stored in the database. The image data is bound to the entity instance named newGolfer and automatically persisted to the database along with the other fields on this entity. If you’re content with the image as it’s uploaded, your work is done. However, it’s likely that you’ll want to put some limits on what the user can upload.

Controlling what gets uploaded

The accept attribute on <s:fileUpload> is used to specify a comma-separated list of standard Multipurpose Internet Mail Extensions (MIME) types that can be uploaded. The upload field in the registration form limits the acceptable file types to graphic formats that Seam is capable of rendering dynamically. The use of wildcards is also permissible. You could accept all image MIME types, for instance, using the pattern image/*. Even with this restriction in place, though, you should still validate the file type in the action method.

Seam exposes two global settings on the built-in component named multipartFilter to control file uploads. The maxUploadSize property allows you to cap the size (in bytes) of the file being uploaded. There’s no limit in the default configuration. You use the createTempFiles property to control whether Seam uses a temporary file to store the uploaded file data or whether the file data is held in memory, which is the default. These two properties can be adjusted using component configuration as follows:

  <web:multipart-request max-upload-size="5242880" create-temp-files="true"/>

While the maxUploadSize property puts a limit on file size of the uploaded profile image, it doesn’t put restrictions on its dimensions. Once the profile image has uploaded, it’s a good idea to scale it so that it doesn’t steal too much space on the page when rendered.

Processing an uploaded image

The uploaded image can be resized in the action method before the newGolfer instance is persisted. The class org.jboss.seam.ui.graphicImage.Image, which is bundled with the jboss-seam-ui.jar file, makes resizing and scaling images a cinch. Listing 13.1 shows the code added to the register() action method that manipulates the uploaded image. This code is just a starting point. If you use it in your application, you’ll likely want to make it more configurable by eliminating the hardcoded values you see used here.

Listing 13.1. Resizing the profile image

Once you’ve accepted the raw file data into the database, you need to render it. After all, what good would it be? Seam can render raw file data in addition to static files read from the classpath and input streams, in any of the following ways:

  • As an image in a web page
  • As an image in a PDF document
  • Pushed to the browser to be downloaded
  • As an inline image in an email or as an email attachment

Let’s begin by exploring how to render the raw image data in a web page using Seam’s enhanced graphic UI component. As the chapter progresses, you’ll learn about the other ways to use the raw file data listed here. These additional options build on this initial lesson in that they’re merely variations on the graphic UI component.

13.1.2. Rendering images from raw data

The Seam UI component set includes an enhanced graphic component capable of operating on a dynamically generated image. Seam’s graphic component, <s:graphicImage>, is an extension of the standard JSF graphic component, <h:graphicImage>. In addition to the features supported by the standard component, Seam’s version has support for rendering raw image data and performing image transformations.

Rendering dynamic images with Seam’s enhanced graphic UI component

The standard <h:graphicImage> tag only accepts a string value or an EL value expression that resolves to a string value. This value is used to serve a static graphic resource from the web application context (e.g., /img/golfer.png). The <s:graphicImage> tag supports a much broader range of Java types resolved from an EL value expression. Table 13.1 lists the supported dynamic Java types from which the <s:graphicImage> can read image data and the image MIME types that the component can handle.

Table 13.1. The Java types and MIME types supported by <s:graphicImage>

Supported Java types

Supported MIME types

String (any classpath resource) image/png
byte[] image/jpeg (or image/jpg)
java.io.File image/gif
java.io.InputStream  
java.net.URL  

Like the <h:graphicImage> component, the <s:graphicImage> produces a standard HTML <img> element. The difference is that Seam generates a random filename for the image, which is used in the src attribute of the <img> tag and served by the SeamResourceServlet. If you don’t want the filename to be random, you can specify a fixed filename in the fileName attribute. You don’t need to put the extension of the image in the filename; Seam appends the extension automatically according to image type.

You should recognize that one of the supported Java types listed in table 13.1 is byte[], which is the property type that holds the golfer’s profile image. Let’s use the <s:graphicImage> component tag to display the profile image uploaded during the registration process on the golfer’s profile page. The image data is specified in the value attribute. A fallback image is used if the golfer doesn’t have a profile image. The golfer’s username is used as the filename of the image to produce a URL that remains stable and thus allows the browser to cache the image. Finally, alternate text is provided for browsers that can’t render images:

  <s:graphicImage value="#{selectedGolfer.image ne null ?
      selectedGolfer.image : '/img/golfer.png'}"
    fileName="#{selectedGolfer.username}"
    alt="[profile image]"/>

In this example, we display image data retrieved from the database. However, you can also use a Seam component to create an image using Java 2D. The image can be prepared in a Seam component, converted to one of the accepted Java types listed in table 13.1, and then bound to the <s:graphicImage> tag. But before you venture into Java 2D, you may be able to leverage one of the basic image transformations that Seam provides.

Image transformations

Given that the <s:graphicImage> component loads image data into memory, it stands to reason that the image can be transformed prior to being rendered. One of three transformation component tags can be nested within <s:graphicImage> to apply transformations to the image using Java 2D, which are listed in table 13.2. Each component tag accepts one or more parameters that control how the transformation is applied. These transformations are the same as those provided by the Image class from the Seam API introduced in section 13.1.

Table 13.2. Transformation components that can be used with <s:graphicImage>

Component tag

What it does...

Parameters

<s:transformImageSize> Resizes the image to a specific height, width, or both. The aspect ratio can be fixed if scaling is performed on a single dimension. width height maintainRatio factor
<s:transformImageType> Converts the image to either PNG or JPEG. contentType
<s:transformImageBlur> Performs a blur on the image. radius

The golfer’s profile image is reduced to a reasonable size when it’s initially uploaded. But it may be necessary to scale it even further to create a thumbnail for personalizing a review or comment that the golfer posts somewhere on the site, as shown here:

  <s:graphicImage value="#{_review.reviewer.image ne null ?
      _review.reviewer.image ? '/img/golfer.png'}"
    fileName="#{_review.reviewer.username}-36-thumbnail"
    alt="[thumbnail of profile image]">
    <s:transformImageSize width="36" maintainRatio="true"/>
  </s:graphicImage>

You can easily create your own transformation component by implementing the interface org.jboss.seam.ui.graphicImage.ImageTransform. This interface has one method, applyTransform(), which accepts the Image type from the Seam API that you worked with in section 13.1. To make your component available to JSF, you have to go through the song and dance of setting up a JSF component. If you’re going to do so, check out the source code in the Seam UI module and have a good JSF reference close by such as JavaServer Faces in Action (Manning, 2004) or Pro JSF and Ajax (Apress, 2006). To save yourself time, take advantage of the Ajax4jsf Component Development Kit (CDK).

You’ll have the opportunity to visit the <s:graphicImage> tag again when you learn how to send emails with Seam in section 13.4. There’s an equivalent graphic component for embedding dynamic images in a PDF document. Speaking of PDF, it’s time to move away from HTML and explore how to create PDF documents dynamically.

13.2. PDF generation with iText

You may be wondering, “What can an application framework do to help me create PDF documents?” After all, most other frameworks provide a halfhearted integration attempt by only helping you serve the PDF to the browser, leaving the work of creating the PDF up to you. Well, with Seam, the answer to this question is plenty.

You see, Seam goes well beyond just playing matchmaker between the browser and the PDF renderer. In Seam, creating and serving a PDF is handled just like any other JSF view. Seam provides a set of UI component tags that generate PDF content. When the template is processed, the view handler serves a PDF document to the browser, generated by the open source (LGPL/MPL) iText Java-PDF library, rather than an HTML document.

The PDF component tags extend from the tag handler in the Facelets API, so you must use Facelets to generate PDF documents in this way. To enable PDF support in your application, you need to add two files to the application classpath: itext.jar and jbossseam-pdf.jar (both of which you’ll find in the lib folder of projects generated with seamgen). You can then begin using the PDF component tags in your Facelets templates.

13.2.1. Laying out a PDF with UI components

A Facelets template that renders a PDF document uses <p:document> as the root tag. Aside from the new root tag and the accompanying palette of PDF tags, there’s no difference in how you develop it compared to any other Facelets template. You can use Facelets and Seam composition tags (e.g., <ui:composition>, <s:decorate>), non-rendering JSF component tags (e.g., <h:panelGroup>, <s:fragment>), and JSF component tags that produce HTML to build the JSF UI component tree. Since the PDF template is rendered by JSF just like any other JSF view, you can front-load the request with Seam’s page-oriented controls (page actions, page parameters, and page restrictions). That’s a pretty powerful combination. Notice that nowhere in that description did I mention Java. In this scenario, we want to avoid the use of Java to reuse our Facelets knowledge to create dynamic views. There’s no need to step into a Java API to perform this work.

For the most part, the PDF component tags map one to one with the functionality provided by iText. An iText PDF document rendered through Seam consists of paragraphs, images, headers, footers, chapters, sections, tables, lists, bar codes, and even Swing components. You can customize the font size, font color, and background color on most elements. Some limitations exist, but the PDF component tags should be sufficient for all but the most complex requirements.

Rather than itemize each and every tag in the PDF component palette, I provide a comprehensive example that puts many of the tags to use. This approach will give you real-world experience with the PDF tags, which you can supplement by consulting the reference documentation for the specifics of each tag. In this example, we generate a scorecard in PDF format for a golf course. The scorecard is the grid of holes and tee sets that you use to record the number of strokes you took on each hole. It’s fairly complex to render, but also aesthetically pleasing. Thus, I guarantee that this will be a rewarding experience.

Setting up for the scorecard

To display the full scorecard for a course, it’s necessary to use all the entities in the golf course model: Facility, Course, Hole, TeeSet, and Tee. The associations between these entities are configured to be lazy loaded. However, as you’ve learned, it’s best to avoid lazy loading in cases when using it wouldn’t be efficient. For instance, rendering the scorecard would cause a large number of lazy associations to be crossed, in turn causing a lot of queries. To optimize, we want to use a page action to eagerly fetch all the necessary data in a single query and then make that data available to the Facelets template. The Scorecard component, shown in listing 13.2, handles this preload logic in the load() method. The abundance of join fetch clauses in the JPQL that’s executed in this method represents the eager fetching of the associations.

Listing 13.2. The component that eagerly fetches the scorecard data
  @Name("scorecard")
  public class Scorecard extends EntityController {
      private static final String JPQL =

          "select distinct c from Course c " +
          "join fetch c.facility join fetch c.holes " +
          "join fetch c.teeSets ts join fetch ts.tees " +
          "where c.id = #{scorecard.courseId}";

      @RequestParameter private Long courseId;

      @Out private Course course;

      public void load() {
          course = (Course) createQuery(JPQL).getSingleResult();
      }

      public List<TeeSet> getTeeSets() { ... };
      public List<TeeSet> getMensTeeSets() { ... };
      public List<TeeSet> getLadiesTeeSets() { ... };
      public List<Integer> getHoleNumbersOut() { ... };
      public List<Integer> getHoleNumbersIn() { ... };
      public List<Hole> getHolesOut() { ... };
      public List<Hole> getHolesIn() { ... };
      public List<Tee> getTeesOut(TeeSet teeSet) { ... };
      public List<Tee> getTeesIn(TeeSet teeSet) { ... };
  }

The Scorecard component also provides a handful of utility methods needed to render portions of the scorecard. The implementation details aren’t important to this discussion, so the method bodies are hidden (you can see them in the book’s source code). The use of the terms out and in represent the two halves of the golf course. Out is the first nine holes, leading away from the clubhouse. In is the back nine holes, returning to the clubhouse. The methods getTeesOut() and getTeesIn() are invoked from the Facelets template using a parameterized value expression.

A scorecard is complex and so is the Facelets template needed to generate it. We get there in two phases. In the first phase, we get our feet wet with a simple PDF report.

A basic PDF report

The first step is to create the Facelets template exportCourseInfo.xhtml, shown in listing 13.3. This template renders basic information about a course and the facility logo. Notice that the root of the template is <p:document> and that the template declares the following namespace, which imports the PDF UI component tags:

Listing 13.3. A simple PDF template that renders text, an image, and a list

  p:xmlns="http://jboss.com/products/seam/pdf"

Next, we connect a page action to this view ID to preload the scorecard data, defined in the exportCourseInfo.page.xml descriptor:

  <page action="#{scorecard.load}"/>

The page action isn’t a prerequisite for rendering a PDF, but it’s relevant in this scenario.

As you can see in listing 13.3, it doesn’t take much to create a PDF document. The <p:document> tag notifies the view handler to initialize a new iText PDF document. If used alone, this tag will produce an empty document and push it to the browser. A wide range of optional attributes are available on the <p:document> tag that you can use to adjust the properties of the PDF document, such as title, subject, author, keywords, and creator. You can also change the orientation and size of the page. The default page size is A4, but here it has been changed to LETTER .

iText documents are optimized to be rendered as PDF, the default, but can also produce RTF or HTML. You can set the output format using the type attribute , which accepts three values: pdf, rtf, and html. Here, the output format is controlled by the request parameter type, if present.

 

Note

The RTF and HTML output formats support the same features as PDF with the exception of tables, images, and inline HTML. If any of these features are present in the template, they’re ignored when the document is rendered.

 

Moving on to the content of the document, the template includes one image , three paragraphs , and one bulleted list . The <p:image> tag works just like the <s:graph-icImage> tag. It can read images from the Java types in table 13.1 and can apply image transformations. The <p:image> tag is used here with <s:transformImageSize> to reduce the height of the image to 96 pixels, but the tag also has built-in scaling functionality. The <p:font> tag applies font settings to all descendent tags until another <p:font> tag is encountered that alters those settings. All inline text must be enclosed in a <p:paragraph> tag or strange things occur (exceptions include <p:header>, <p:footer>, and <p:listItem>). You can also use a <p:font> tag within a span of paragraph text to change the font characteristics for a single word or phrase.

 

Note

There are some cases when the font settings aren’t inherited by a nested <p:font> tag. For instance, if you use a <p:font> around a <p:list> and then use the <p:font> to customize the contents of a <p:listItem>, the font settings from the outer tag—such as font size—aren’t inherited, forcing you to have to apply them again on the nested tag. Expect this to be fixed in the future.

 

It’s possible to use the Facelets iteration component tag to generate branches of the component tree dynamically. In this case, we iterate over the tee sets on the course and display the tee set name and total distance in yards of each set as list items. The final result is shown in figure 13.1.

Figure 13.1. A PDF document showing basic course and tee set information

As you can see, creating a report in PDF format is no more difficult than creating a web page. But you aren’t done yet. Most reports that you have to build probably require some sort of tabular data. Seam offers a set of component tags for creating PDF tables that make it no more difficult than using the JSF panel grid component for rendering HTML tables. We put these PDF table tags to the test by rendering a complete course scorecard, which shows the tee sets as well as the distances for each hole in a tee set.

13.2.2. Working with tables and cells

PDF tables are created in Seam using the <p:table> and <p:cell> component tags. The <p:table> tag works in precisely the same way as the <h:panelGrid> tag from the standard JSF component palette, except that the child components must be wrapped in a <p:cell> tag. You explicitly state how many columns the table has by using the columns attribute. Once an equivalent number of <p:cell> tags have been encountered, a new row is started. The contents of a <p:cell> tag can be another table, giving rise to nested tables. A single cell can be made to span multiple columns using the colspan attribute on <p:cell>. It’s not possible, however, for a cell to span more than one row (i.e., rowspan).

 

Tip

As an alternative to PDF tables, you can use HTML tables. You can use other HTML elements as well, including JSF component tags that produce HTML. To add HTML to the PDF, you nest it within a <p:html> element. Keep in mind that the HTML is converted to iText objects internally, so you are limited to what iText can produce. You can also use <p:swing> for rendering a Swing component and <p:barcode> to create a bar code. The <p:barcode> tag can also be used in an HTML page.

 

To demonstrate the table component tag in action, we use it to help render the scorecard for a golf course. This use case offers enough complexity to show off many of the advanced capabilities of the table tag, rather than having me list them in a table. Before jumping into the template, though, I briefly explain the goal.

The scorecard consists of a single table that’s logically partitioned into two halves. The left side of the card has information about the course’s front nine holes (Out), and the right side of the card has information about the course’s back nine holes (In). The first row displays the hole numbers. Following that row are rows for each tee set. The tee set rows consist of distance values that correspond with each hole number. Finally, there’s a row that displays the par for each hole. The template that produces this markup, exportScorecard.xhtml, is shown in listing 13.4.

Listing 13.4. The PDF template that renders the course scorecard
  <p:document xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://jboss.com/products/seam/pdf"
    title="#{course.name} Scorecard"
    orientation="landscape">     
    <p:font size="8">
      <p:table columns="22" widthPercentage="100" headerRows="1"     
        widths="3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1">
        <f:facet name="defaultCell">     
          <p:cell padding="5" noWrap="true"
            horizontalAlignment="center" verticalAlignment="middle"/>
        </f:facet>
        <p:font size="8" color="white" style="bold">     
          <p:cell horizontalAlignment="left" grayFill=".25">     
            <p:paragraph>Hole</p:paragraph>
          </p:cell>
          <ui:repeat var="_holeNum" value="#{scorecard.holeNumbersOut}">    
            <p:cell grayFill=".25">
              <p:paragraph>#{_holeNum}</p:paragraph>
            </p:cell>
          </ui:repeat>
          <p:cell grayFill=".25"><p:paragraph>Out</p:paragraph></p:cell>
          <ui:repeat var="_holeNum" value="#{scorecard.holeNumbersIn}">
            <p:cell grayFill=".25">
              <p:paragraph>#{_holeNum}</p:paragraph>
            </p:cell>
          </ui:repeat>
          <p:cell grayFill=".25"><p:paragraph>In</p:paragraph></p:cell>
          <p:cell grayFill=".25"><p:paragraph>Total</p:paragraph></p:cell>
        </p:font>
        <ui:repeat var="_ts" value="#{scorecard.mensAndUnisexTeeSets}">
          <p:font size="8">
            <p:cell horizontalAlignment="left"
            backgroundColor="#{_ts.color}">     
            <p:paragraph>#{_ts.name}</p:paragraph>
          </p:cell>
          <ui:repeat var="_tee" value="#{scorecard.getTeesOut(_ts)}">
            <p:cell backgroundColor="#{_ts.color}">
              <p:paragraph>#{_tee.distance}</p:paragraph>
            </p:cell>
          </ui:repeat>
          <p:cell backgroundColor="#{_ts.color}">
            <p:paragraph>#{_ts.distanceOut}</p:paragraph>
          </p:cell>
          <ui:repeat var="_tee" value="#{scorecard.getTeesIn(_ts)}">
             <p:cell backgroundColor="#{_ts.color}">
               <p:paragraph>#{_tee.distance}</p:paragraph>
             </p:cell>
            </ui:repeat>
            <p:cell backgroundColor="#{_ts.color}">
              <p:paragraph>#{_ts.distanceIn}</p:paragraph>
            </p:cell>
            <p:cell backgroundColor="#{_ts.color}">
              <p:paragraph>#{_ts.totalDistance}</p:paragraph>
            </p:cell>
          </p:font>
        </ui:repeat>
        <p:font size="8" style="bold">
          <p:cell horizontalAlignment="left" grayFill=".9">
            <p:paragraph>Par</p:paragraph>
          </p:cell>
          <ui:repeat var="_hole" value="#{scorecard.holesOut}">
            <p:cell grayFill=".9">
              <p:paragraph>#{_hole.mensPar}</p:paragraph>
            </p:cell>
          </ui:repeat>
          <p:cell grayFill=".9">
            <p:paragraph>#{course.mensParOut}</p:paragraph>
          </p:cell>
          <ui:repeat var="_hole" value="#{scorecard.holesIn}">
            <p:cell grayFill=".9">
              <p:paragraph>#{_hole.mensPar}</p:paragraph>
            </p:cell>
          </ui:repeat>
          <p:cell grayFill=".9">
            <p:paragraph>#{course.mensParIn}</p:paragraph>
          </p:cell>
          <p:cell grayFill=".9">
            <p:paragraph>#{course.totalMensPar}</p:paragraph>
          </p:cell>
        </p:font>
      </p:table>
    </p:font>
  </p:document>

The default layout for a document is portrait, but the layout of this document is set to landscape to make room for the scorecard. If a table is wider than the document, the text in each cell is forced to wrap. If wrapping is required but the noWrap attribute on <p:cell> is false, the text in the cell may overrun the cell borders (and look ugly).

The table is declared using the <p:table> tag. The scorecard table has 22 columns and is configured to span the width of the page . The first row is treated as a header, as defined by the headerRows attribute. This row is repeated if the table is divided across a page boundary. The widths attribute dictates the width ratios of the cells. In this example, the first column is three times as wide as the other columns in the table. If the widths attribute isn’t specified, the cells are evenly distributed. As an alternative to using the widths attribute, we could have used 24 columns and added colspan="3" to the first <p:cell> tag in each row to achieve the same effect.

The content of the table is created by repeating the <p:cell> across columns and rows, either explicitly or indirectly using an iteration component . To help with the task of defining cells, the <p:table> tag supports “Don’t Repeat Yourself” (DRY) semantics by offering a cell prototype, declared in the defaultCell facet . You can place all of the attributes you would like to have applied to each <p:cell> in this prototype cell. You can, of course, override these settings as needed in the <p:cell> tag. Here we establish the default padding, wrap behavior, and alignments. You can also surround the whole table, or a series of cells, in a <p:font> tag to have font settings applied to descendent cells.

 

Note

The <p:cell> must have a <p:paragraph> tag as its first and only element. Although the text will still render without being wrapped in <p:paragraph>, the font settings will not be applied to it.

 

The final result of the scorecard is shown in figure 13.2.

Figure 13.2. A PDF document showing a golf course scorecard

The scorecard makes liberal use of color and grayscale shading. To apply a grayscale background to a cell, you set the grayFill attribute on <p:cell> to a value between 0 and 1 (lower is darker). You can also apply colors to text, cell backgrounds, tables, sections, and image borders. The scorecard example makes use of color for the header row text and most of the cell backgrounds . Color is important for making an attractive document, so let’s take a closer look at what types of color values are accepted.

13.2.3. Adding a splash of color

The iText library uses the AWT Color object for applying color to a PDF document or a chart, covered later. Given that you are working in a Facelets template, you need a translation layer. Fortunately, Seam provides one. Seam lets you choose from several color code sets that you can use to specify a color value in a component tag attribute. That value is then translated into a Color object and passed to the iText API. The possible value types you can enter are shown in table 13.3. If you enter an invalid color value, an exception will be thrown when the document is rendered.

Table 13.3. Possible color values used in the PDF or chart components

Type

Identified by

Examples

java.awt.Color constant Lowercase constant name red, green, blue
Hexadecimal number Leading #, 0x, or 0X #FF0000, 0x00FF00, 0X0000FF
Octal number Leading 0 077600000, 0177400, 0377
UIColor JSF client identifier <p:color id="maroon" color="#8B0000"/>

The AWT color constant names are the most convenient approach and should be sufficient if basic colors will do. If you’ve spent a lot of time with Cascading Style Sheets (CSS), you may be fluent in hexadecimal color codes and may choose to use those instead. For those of you who can even comprehend octal numbers, you’ll be glad to know they’re supported too.

You’ve seen many of the features offered by Seam’s PDF component palette in this section. You’ll be excited to hear that equivalent support for creating Microsoft Excel documents is coming your way soon, as part of Seam 2.1. Although the Excel tags aren’t covered in this book, you have the background knowledge you need to be able to use them.

There are some limitations with the template-based approach, but remember that if you find yourself pushing the envelope of what these tags can handle, you can always switch to using the iText API directly. If you do make that switch, you’ll be glad to know that, in addition to the component tags, Seam’s PDF module includes an API for serving PDF documents to the browser. We first look at customizing the document store servlet to handle missing documents and serve friendly file extensions, and then dig deeper into how to use it to serve your own documents.

13.2.4. Graceful failures and friendly file extensions

Seam serves PDF documents from the JSF phase listener DocumentStorePhaseListener. After a document is created from a Facelets template, it’s stored in the built-in component named documentStore under a unique id. Seam redirects to a servlet path that begins with /seam-doc, passing the id in the request parameter docId. The phase listener traps requests matching this path, reads the id from the request parameter, and pushes the document with this id to the browser. Here’s an example of the servlet path for a document:

  /seam-doc.seam?docId=10&cid=3

Notice the conversation token in the URL. The documentStore component is scoped to the conversation. Thus, documents exist for the lifetime of the conversation that created them. If a long-running conversation isn’t active, the document lasts for a logical request (i.e., a temporary conversation). It’s likely that if the user bookmarks the URL of the PDF document, he or she will encounter an error when trying to retrieve the document again because the URL is stale and the document no longer exists. To help the user understand why the request doesn’t work, you can configure a custom error page. First, add the component namespace http://jboss.com/products/seam/pdf, prefixed with pdf, to the component descriptor. Then, configure the documentStore component as follows:

  <pdf:document-store error-page="/missingDoc.seam"/>

It’s also possible to configure this built-in component to switch from using a JSF phase listener to a servlet to serve the PDF document. The benefit of using a servlet is that users won’t see a cryptic /seam-doc.seam?docId=4, but rather a friendly one that ends in the .pdf file extension. There are two steps to making this change. First, add the org.jboss.seam.pdf.DocumentStoreServlet to the web.xml descriptor to trap servlet paths that end in .pdf or .rtf:

  <servlet>
    <servlet-name>Document Store Servlet</servlet-name>
    <servlet-class>org.jboss.seam.pdf.DocumentStoreServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Document Store Servlet</servlet-name>
    <url-pattern>*.pdf</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Document Store Servlet</servlet-name>
    <url-pattern>*.rtf</url-pattern>
  </servlet-mapping>

Then let the document store component know that this servlet is available and can be used by adding the use-extensions attribute to the component configuration:

  <pdf:document-store use-extensions="true" error-page="/missingDoc.seam"/>

When extensions are enabled, Seam prepares the document URL by removing the default suffix from the view ID and replacing it with the file extension of the document. A redirect is then issued to the new path. Here’s an example of the servlet path for the scorecard:

  exportScorecard.pdf?docId=10&cid=3

The filename in the path is completely irrelevant to Seam. The only piece of information that matters is the docId request parameter. However, the friendly URL is extremely relevant to an end user because the document id doesn’t carry any semantic value. For this reason, I highly recommend that you use the file extension feature.

Although the document store component was designed to serve documents generated using the <p:document> tag, you can also use it to serve your own binary documents (e.g., PDF, Word, Excel), either served from a database or that you generate using a document builder API such as iText or Apache POI. On the other hand, if you’re happy using Seam’s PDF support to create documents using Facelets templates, you can skip this next section.

13.2.5. Serving dynamic documents

I promised earlier that you would see other ways to serve a binary document. One option is to create a custom servlet. Seam makes this easy by allowing any Seam component that extends AbstractResource to be used as a servlet. You override the method get-ResourcePath(), which is appended to /seam/resource, to indicate the URL that your servlet handles. You then override the getResource() method to serve the resource. But that still puts a lot of burden on you to prepare the file. There’s an easier way.

In addition to the template-based approach to creating PDF documents, Seam has support for managing a file download, which is tricky to do on your own, especially in a JSF application. The built-in documentStore component handles the dirty work of preparing and serving a file to the browser. You simply inject documentStore into your component, use it to store the file data under a unique id, and then redirect the user to a URL that is processed by the DocumentStorePhaseListener (or DocumentStoreServlet). Seam takes over from there. Listing 13.5 shows an example of this process using raw PDF data. Note that Seam doesn’t import the context variable prefix org.jboss.seam.pdf by default, so you must import it in order to use the unqualified component name documentStore.

Listing 13.5. A component that pushes a dynamic document to the browser

As you can see, Seam provides a convenient way to push binary documents to the browser, comparable to what other frameworks offer. In most cases, though, you won’t use this approach unless you absolutely need the extra power of creating documents in Java. Instead, you’ll take advantage of Seam’s template-based approach, which goes well beyond where other frameworks leave off.

So far you have learned to render dynamic images and PDF documents. Next up is charts, another type of dynamic image. Charts can be rendered in both an HTML page and a PDF document. Not to spoil the excitement, but after the next section, you’ll learn to include images, PDFs, and charts in an email with almost no additional work.

13.3. Quick and easy charting with JFreeChart

Creating charts in Seam is, you guessed it, just another Facelets template—only this time, it’s just a fragment of a template. Learning to use Seam’s charting module is simply a matter of learning how to use the chart-related UI component tags.

Charts provide a visual representation of data sets. In technical terms, that means dynamic graphic generation. Seam’s charting module is based on the open source (GNU General Public License, or GPL) JFreeChart chart library. JFreeChart offers a wide variety of chart types and can render them in several image formats. At the time of this writing, Seam only offers a subset of the JFreeChart functionality, but the idea is to eventually bring most of the chart types under the wings of Seam’s Facelets-based infrastructure. Seam currently supports bar charts, pie charts, and line plots, all of which are rendered in JPEG format.

To enable charting support in your application, you add jfreechart.jar and jcommon.jar to the application’s classpath. Then, it’s just a matter of hooking the UI component tags up to data. Seam’s charting support is bundled in Seam’s PDF module, and the component tags share the same UI component set. That doesn’t mean you can only use charts in PDF documents, though. They can be rendered in HTML pages too!

13.3.1. Chart basics

To add a chart to your Facelets template, you first register the UI component library in the root element of the template using the same namespace as for the PDF component tags:

  xmlns:p="http://jboss.com/products/seam/pdf"

The charts that Seam supports, along with the corresponding component tag, are as follow:

  • Bar chart—<p:barchart>
  • Pie chart—<p:piechart>
  • Line chart—<p:linechart>

Each chart consists of several common configuration elements, including title, legend, orientation, width, height, and a wide variety of paint, stroke, gridline, and border display options. If you’re a fan of cool graphics, your favorite display option might be the 3D feature, which gives the chart visual depth. Appearances aside, the most important aspect of every chart is the data.

A chart consists of one or more sets of data, represented by the <p:series> tag, and one or more nested data points in each series, represented by the <p:data> tag. The tags can map one to one with the data displayed, or you can nest either of them in a repeating component, such as <ui:repeat>, to render a dynamic collection of data.

The crowning feature of charts is that they are eye-catching. Otherwise, tabular data would do just fine. That means adding a splash of color. There are attributes on each component tag that allow you to specify the color for various regions of the chart. These attributes all end in Paint. The JFreeChart library also uses the AWT Color object for defining colors. Thus, Seam offers the same translation for color values in charts as it does for PDF documents. Refer back to table 13.3 for the possible color values.

I now take you through each chart type, demonstrating how to use the <p:series> and <p:data> tags, as well as several of the aesthetic configurations. The examples shown pertain to golf rounds, which were added to the application in chapter 10. Let’s start with bar charts.

13.3.2. Bar charts

A bar chart can be viewed as a series of buckets. Each bucket is filled proportionally to the value being represented. The purpose of a bar chart is to compare one or more values. A complete set of the different buckets is referred to as a series. There can be more than one series on the same chart, each representing some data variation. For instance, if the buckets represented golf pro shop sales, there would be buckets for balls, shirts, and shoes. The series could represent a single day’s sales.

In Seam’s charting component palette, each bucket is represented by a <p:data> tag. The key attribute on this tag represents the name of the bucket (the sale item), and the value specifies how much it is filled (how many sold). The <p:data> tags are grouped in a series as children of the <p:series> tag. The key attribute on the <p:series> tag is the data variation (the date of sale). The <p:series> tag is the child of the chart tag, in this case <p:barchart>.

Taking an example from the Open 18 application, we look at the average score (average number of strokes taken) versus par (the number of expected strokes for a given hole) for a round. We group all of the holes with the same par together and look at what the golfer scored on average. The title of the chart is “Average Score vs. Par.” In this case, the bucket is the par value and how much the bucket is filled is the average score. A very basic version of this chart is shown here:

  <p:barchart title="Average Score vs. Par" rangeAxisLabel="Avg Score">
    <p:series key="#{round.date}">
      <p:data key="Par 3" value="#{roundHome.getAverageScore(3)}"/>
      <p:data key="Par 4" value="#{roundHome.getAverageScore(4)}"/>
      <p:data key="Par 5" value="#{roundHome.getAverageScore(5)}"/>
    </p:series>
  </p:barchart>

The default size of the chart in pixels is 400x300, which can be overridden using the width and height attributes. Don’t worry right now how the average scores are calculated because that’s tangential to how the bar chart works. This chart has one series, named after the date that the round was played. You must always provide at least one series. If you have only one series, and the name isn’t relevant, you can hide its presence by keeping the legend disabled. If you have more than one series, the legend is used for the purpose of identifying each series according to its color.

Let’s add another series by comparing the golfer’s round with the average of all of that golfer’s rounds to date. In this case, there are two series, so the legend is needed to distinguish them from one another.

  <p:barchart title="Average Score vs. Par" rangeAxisLabel="Avg Score"
    legend="true" is3D="true" plotForegroundAlpha=".9">
    <p:series key="#{round.date}" seriesPaint="series1">
      <p:data key="Par 3" value="#{roundHome.getAverageScore(3)}"/>
      <p:data key="Par 4" value="#{roundHome.getAverageScore(4)}"/>
      <p:data key="Par 5" value="#{roundHome.getAverageScore(5)}"/>
    </p:series>
    <p:series key="#{round.golfer.username}'s rounds" seriesPaint="series2">
      <p:data key="Par 3" value="#{golferRounds.getAverageScore(3)}"/>
      <p:data key="Par 4" value="#{golferRounds.getAverageScore(4)}"/>
      <p:data key="Par 5" value="#{golferRounds.getAverageScore(5)}"/>
    </p:series>
    <p:color id="series1" color="#FFBF4F"/>
    <p:color id="series2" color="#A6C78E"/>
  </p:barchart>

The rendered output of the Average Score vs. Par chart is shown in figure 13.3.

Figure 13.3. A bar chart with two series generated using the Seam chart component tags

There are now two series in this chart, a legend, and some flare, the later provided by the 3D flag, the alpha transparency, and the custom bar colors. Although not shown in these examples, I tend to use the following options as a starting point to clear the default canvas (border and background) that’s applied to the charts:

  borderVisible="false"
  borderBackgroundPaint="white"
  plotOutlinePaint="white"
  legendOutlinePaint="white"

(legendOutlinePaint applies to bar and line charts only.)

Many other properties are available for customizing the look of a chart. At this point, you have enough to run with. In fact, with bar charts under your belt, we can cruise through the next two chart types because they are much the same, starting with line charts.

13.3.3. Line charts

Line charts are similar to bar charts except that the points in a series are connected together rather than filled from the base. If you’ve ever had to work on a spreadsheet assignment in a subject like economics, you’ve probably created at least one if not a hundred line charts. Fortunately, creating them in Seam is no more difficult than creating them in a spreadsheet.

Line charts are ideal for showing trends. In the Open 18 application, trends can be used to show a golfer’s progress from one round to the next, such as putting average and strokes over par. First, we need to retrieve the golfer’s rounds. Assuming the context variable selectedGolfer is available on the current page, we can use it in a restriction clause of a Query component, defined in the component descriptor, that fetches the golfer’s rounds:

   <framework:entity-query name="golferRounds"
     ejbql="select r from Round r join fetch r.scores" order="r.date asc">
     <framework:restrictions>
       <value>r.golfer = #{selectedGolfer}</value>
     </framework:restrictions>
   </framework:entity-query>

The statistics for each round are represented as a series (a line) on the chart. The individual data points are taken from each round and plotted progressively over time according to the date the round was played. To plot the points for each round, we use an iteration component to loop through the rounds for a golfer and read the data points from each Round instance:

  <p:linechart title="Game Analysis" domainAxisLabel="Date of round"
    legend="true">
    <p:series key="Putting average">
      <ui:repeat var="_round" value="#{golferRounds.resultList}">
        <p:data key="#{_round.date}" value="#{_round.averagePutts}"/>
      </ui:repeat>
    </p:series>
    <p:series key="Strokes over par">
      <ui:repeat var="_round" value="#{golferRounds.resultList}">
        <p:data key="#{_round.date}" value="#{_round.strokesOverPar}"/>
      </ui:repeat>
    </p:series>
  </p:linechart>

The rendered output of the game analysis chart is shown in figure 13.4. In this example, the labeling of the chart is reversed from what it was in the bar chart. The range axis is unlabeled since the purpose of the values varies according to the series, making the legend very important. If you’re plotting series that represent the same type of data, it makes sense to label the range axis. The domain axis is labeled to indicate that the dates represent the date the round was played. As the range of data changes, the chart automatically scales itself accordingly. While automatic scaling is sometimes desirable, there may be times when you need to fix the range. Unfortunately, the component tags don’t provide a way to customize the default behavior.

Figure 13.4. A line chart with two series generated using the Seam chart component tags

All that’s left is the simplest and most universal of all the charts: the pie chart.

13.3.4. Pie charts

No matter how you divide it up, a pie chart adds up to 100 percent. Each slice represents the percentage of the whole an item accounts for. The slices are each assigned a unique color and identified by a label. You can have as many wedges as you want, but over a certain point you start to get diminishing returns because the chart becomes too difficult to read.

Pie charts, by nature of design, only represent a single series of data. Therefore, you only need to use <p:data> tags. (If a <p:series> tag is present, it’s ignored.) Each <p:data> tag represents one slice of the pie. The value of each slice is treated as a weight, not a percentage. The percentage is assigned to each slice automatically, calculated by dividing the slice’s value by the total value of all of the slices. (It’s impossible to exceed 100%.)

In the Open 18 application, we provide users with a pie chart to help them analyze their putting. Each wedge in the pie chart represents the number of strokes it took the user to get the ball in the hole, and the size of the wedge is how often that number of strokes was taken. The value expression #{roundHome.puttFrequencies} returns a collection of PuttFrequency objects. The PuttFrequency class has two properties, numPutts and count, which are used as the key and value of the data point, respectively. Since the user can take any number of putts, and some numbers may not be used, we use an iteration component to render the data points:

  <p:piechart title="Putt Analysis" legend="false"
    circular="true" is3D="true" plotForegroundAlpha="0.9">
    <ui:repeat var="_freq"
      value="#{roundHome.puttFrequencies}">
      <p:data key="#{_freq.numPutts} putt" value="#{_freq.count}"/>
    </ui:repeat>
  </p:piechart>

The rendered output of the putt analysis chart is shown in figure 13.5. Because a pie chart uses labels for each of the slices, there’s no need for a legend, so it’s disabled.

Figure 13.5. A pie chart generated using the Seam chart component tags

You’ve seen several examples of how you can create dynamic graphics and documents simply by using JSF component tags. You can make the graphics even more dynamic by using the Ajax-driven partial page rendering covered in the previous chapter.

Then you can manipulate the settings in one part of the page and see the image, such as a chart, update instantly. That’s one of the main benefits of using JSF component tags to create graphics.

Up to this point, everything in this chapter has catered to the browser, either to upload a file, render a graphic, or to serve a file to the browser as a download. But guess what? You can generate and send emails following this philosophy. In fact, you can even send dynamic graphics and documents along with the email as attachments. You are practically there already. There’s just a little bit of additional knowledge to fill in.

13.4. Composing email the Seam way

To send an email in Seam, you can use a Facelets template just as you do to create a PDF document. Once again, it’s just like any other JSF view; however, once the template is processed, the view handler sends an email using a JavaMail Session rather than serving an HTML response to the browser. To support this feature, there’s another set of UI component tags oriented toward email composition. These tags extend from the tag handler from the Facelets API, so you must be using Facelets to send email this way. Let’s try it out.

13.4.1. Sending your first message

An email template differs slightly from its PDF counterpart—it doesn’t send any response to the browser. Instead, the root email component tag, <m:message>, is treated like a utility tag in that it performs an action. Although it does render its children, that result is buffered into an email message.

When the start tag is encountered during the rendering process, a new email message object, MimeMessage, is instantiated. The tags nested within <m:message> contribute to the MimeMessage object. When the end tag is encountered, the object is passed to the JavaMail Transport object, which sends the message off to the recipients.

It’s possible to embed the <m:message> tag in any Facelets template, which would result in an email message being sent when that page is rendered. In practice, however, you typically create a stand-alone template reserved for composing an email and then call on that template from Java when the message needs to be sent. We’ll get to that in a moment.

Let’s consider an example to demonstrate how the process of composing and sending an email works. After new golfers register, we want to send them an email welcoming them to the community and encouraging them to participate. Listing 13.6 shows a plain-text email containing the welcome message and authentication credentials for new golfers.

Listing 13.6. A plain-text email message template
  <m:message xmlns="http://www.w3.org/1999/xhtml"      
     xmlns:m="http://jboss.com/products/seam/mail"
     importance="normal">
     <m:header name="X-Composed-By" value="JBoss Seam"/>    
     <m:from name="Open 18" address="[email protected]"/>    
     <m:replyTo address="[email protected]"/>
      <m:to name="#{newGolfer.name}">#{newGolfer.emailAddress}</m:to>     
      <m:subject>Open 18 - Registration Information</m:subject>     
      <m:body type="plain">#{newGolfer.name},     

   Welcome to the Open 18 community!

   Thank you for registering. On behalf of the other members, I would
   like to say that we look forward to your participation in the Open
   18 community.

   Below is your account information. Please keep it for your records.

   Username: #{newGolfer.username}
   Password: #{passwordBean.password}

   Your password has been encrypted in our database...

   Open 18
   ...a place for golfers
   Member Services</m:body>
   </m:message>

The <m:message> tag indicates that this component tree fragment is responsible for producing and sending an email message. The <m:message> tag supports the configuration of a number of standard email headers using the attributes importance, precedence, and requestReadReceipt. Additional headers can be added using the <m:header> tag . All email messages must specify a sender , a recipient , a subject , and a body . Any number of <m:cc> or <m:bcc> tags can be included to add Cc and Bcc recipients to the message, respectively.

The body of the message is assumed to be HTML unless specified as plain text using the type attribute on <m:body>. You can send both an HTML and plain-text body so that the email client has a choice of which one to render, shown here. The HTML part is placed directly inside the <m:body> tag and the alternative part, assumed to be plain text, is placed within a facet named alternative.

   <m:body>
     <html>
       <body>
         <p>#{newGolfer.name},</p>
         <p><b>Welcome to the <span style="color: green;">Open 18</strong>
   community!</b></p>
         ...
       </body>
     </html>
     <f:facet name="alternative">#{newGolfer.name},
   Welcome to the Open 18 community!
       ...
     </f:facet>
   </m:body>

The rendered HTML version of the welcome email is shown in figure 13.6. You’ll see later on how the inline image gets inserted.

Figure 13.6. An HTML email created by Seam’s mail component that includes an inline image

The email template appears simple enough, but we haven’t addressed when the template is activated and how it gains access to the context variables newGolfer and passwordBean. This is where Seam’s email support shows its uniqueness.

Getting the email to go through

If you’ve worked with JSP in the past, you may shudder at the idea of embedding email logic in a view template, as we are doing here. It’s not that drafting an email using a template is inherently bad; it’s that without some back-breaking effort, it’s not possible to have the application code render a JSP template on demand. It is possible to do this with a Facelets template, though. That’s exactly how you “send” an email: you render it.

The rendering is handled by Seam’s Renderer component, named renderer, which is typically invoked from within an action method. The template can access any context variables that are in scope at the time the renderer is invoked. The RegistrationMailer component, shown here, can be injected into the RegisterAction component and invoked from the register() method. The newGolfer and passwordBean components are still in scope, so they can be accessed from the email template. The render() method doesn’t return anything because the rendered content is swept away to the email transport.

  package org.open18.action.mail;
  import ...;
  import org.jboss.seam.faces.Renderer;

  @Name("registrationMailer")
  public class RegistrationMailer() {
      @In private Renderer renderer;

      public void sendWelcomeEmail() {
          renderer.render("/email/welcome.xhtml");
      }
  }

You may worry that if the mail server is on a coffee break when the user submits the registration form, the user’s browser will hang until the mail server comes back. To avoid this scenario, you can have the email sent asynchronously. Creating an asynchronous method in Seam is so trivial you almost feel like you’re cheating. Asynchronous tasks aren’t covered in this book, but I want to give you a glimpse of how they’re initiated. You just add the @Asynchronous annotation to the method:

  @Asynchronous void sendWelcomeEmail() { ... }

That’s it! There’s nothing else to set up. By default, Seam uses the Java 5 concurrent library to execute the method in a background thread. The only caveat is that, in Seam 2.0, you cannot use JSF component tags (other than the mail tags) in the template since the rendering occurs in a mock JSF environment. This feature will be available in Seam 2.1.

If the mail server can’t be contacted, or otherwise fails to send the message, the render() method throws a javax.mail.MessagingException wrapped in a javax.faces.FacesException. Notice, however, that there’s absolutely no reference to the JavaMail API (or email helper library) in the code that sends the message. This makes email very noninvasive and easy to test.

Testing email messages

Because the life cycle leading up to the email being rendered is the same as with a normal JSF page, you can test an email using SeamTest. In fact, SeamTest even includes the convenience method getRenderedMailMessage(), which parses the email template passed in as a parameter and returns the resulting MimeMessage object. You can use this object to verify the headers and structure of the message. Keep in mind that in Seam 2.0, SeamTest has the same limitation as sending email asynchronously: the JSF components aren’t rendered. Thus, you can’t fully test the rendered output of the email.

To avoid spending a lot of time sending messages to yourself to verify the contents, I recommend that you make the body of the message a separate Facelets composition template. Then include it in a normal JSF page and inspect the result in the browser. You can even use a tool like Selenium[2] to validate the output. When you’re happy with what it produces, just include it inside the <m:body> tag in the email template. You may still have to do manual testing, but at least this trick gets you most of the way there.

2 Selenium is a browser-based testing tool. It can be downloaded from http://selenium.openqa.org.

What’s great about using Seam’s email integration is that you don’t even have to think about the mechanics of sending the email. Seam is your administrative assistant. You just say, “Go send that email” and it’s done. With all the extra time on your hands, you may want to get more mileage out of your messages by adding attachments, using inline images, and leverage all of the Facelets composition techniques you’ve learned to appreciate.

13.4.2. Adding an entourage to the message

Nothing puts the unnecessary complexities of technology in perspective like having a requirements meeting with a non-tech-savvy client. You’ve been there. A client asks for something that is, logically, a very simple task. Yet, for one reason or another, implementing it costs you a solid day’s work or more. Knowing that, your response is “No way” or “That’s going to be expensive.” One such scenario is email attachments. In theory, it should be so simple. Here’s the file, here’s the email address. Put them together. But there are so many subtle complexities that it never is quite that simple.

Static attachments

You saw an example of how Seam erases complexity in the “Accepting file uploads” section. Seam does it again with email attachments. Attaching a static file to an email using Seam’s email support is as simple as mashing two components together. Let’s assume that marketing wants to attach a flyer to the welcome email that gives an overview of Open 18. It can be added by placing the <m:attachment> tag just above the <m:body> tag:

  <m:attachment value="/open18-flyer.pdf" contentType="application/pdf"
    fileName="About Open 18.pdf"/>
  <m:body>Dear #{newGolfer.name}, ...

The structure of the <m:attachment> tag is almost identical to the <s:graphicImage> tag, covered earlier. In fact, the value attribute accepts all of the Java types shown in table 13.1. The only catch with <m:attachment> is that you must specify the content type and append the file extension to the alternate filename.

Dynamic attachments and embedded images

Let’s send the golfer’s profile image to demonstrate creating an attachment from raw file data (i.e., byte[]). First, add a convenience method to Golfer to get the image extension:

  @Transient
  public String getImageExtension() {
      return Image.Type.getTypeByMimeType(imageContentType).getExtension();
  }

Next, reference the image data in the value of the attachment and specify the content type:

  <m:attachment value="#{newGolfer.image}"
    contentType="#{newGolfer.imageContentType}"
    fileName="#{newGolfer.username}#{newGolfer.imageExtension}"
    rendered="#{newGolfer.image != null}"/>

How about instead of sending the golfer his or her own profile image, we send the profile images of other recently registered golfers? You can call on the newGolfers context variable prepared in chapter 6 and iterate over it using the Facelets iteration component:

  <ui:repeat var="_golfer" value="#{newGolfers}">
    <m:attachment value="#{_golfer.image}"
      contentType="#{_golfer.imageContentType}"
      fileName="#{_golfer.username}#{_golfer.imageExtension}"
      rendered="#{_golfer.image != null and _golfer != newGolfer}"/>
  </ui:repeat>

Adding an image as an attachment is not enough for most email readers to render it automatically. Even if it’s rendered, it is grouped at the bottom of the email with all of the other attachments. It would better to have the image displayed within the body of the message. To do this, you start by setting the disposition of the attachment to inline and designating a status variable that holds information about the inline attachment:

  <m:attachment value="#{newGolfer.image}"
    contentType="#{newGolfer.imageContentType}"
    fileName="#{newGolfer.username}#{newGolfer.imageExtension}"
    disposition="inline" status="profileImageAttachment"/>

You then embed an <img> tag in the body of the message that references the inline attachment using a special URL scheme. The URL consists of the scheme cid: followed by the attachment’s content id, which is read from the status variable of the attachment:

  <p><img src="cid:#{profileImageAttachment.contentId}"/></p>

You can even use the <m:attachment> in the body of the message so that you can render images in a loop. The only requirement is that you declare the attachment before trying to access its status variable:

  <ui:repeat var="_golfer" value="#{newGolfers}">
    <m:attachment value="#{_golfer.image}"
      contentType="#{_golfer.imageContentType}"
      fileName="#{_golfer.username}#{_golfer.imageExtension}"
      rendered="#{_golfer.image ne null and _golfer ne newGolfer}"
      disposition="inline" status="profileImageAttachment"/>
    <p><img src="cid:#{profileImageAttachment.contentId}"/></p>
  </ui:repeat>

If all of this inline disposition stuff seems like too much trouble, or you are concerned it will make the size of the email message too large, you have the option of serving images (and other assets such as style sheets) as linked resources just like in a web page. Let’s say you want to include the logo for Open 18 in the message. First, add it somewhere in the body:

  <h:graphicImage value="/img/logo.png"/>

At this point, the email client isn’t going to know how to find the image based on this relative path, so you have to give it some context. You set the absolute base URL for linked resources using the urlBase attribute on the email message component tag:

  <m:message ... urlBase="http://open18.org">...</m:message>

The value of urlBase is used before the application’s context path (e.g., /open18). In this example, the URL of the logo image is http://open18.org/open18/img/logo.png. You can use EL notation to calculate a base URL instead of hardcoding it, as I do here.

Attachments using compositions

But wait! There’s more to attachments. You can supply a body to the <m:attachment> tag. Within that body you can put plain text, HTML, or even a PDF document. And since these are Facelets templates, that means you can easily insert the contents of another template into this spot. Let’s give the user the ability to send a course’s scorecard to a friend. The scorecard is rendered within the attachment tag and then attached to the email:

  <m:message xmlns="http://www.w3.org/1999/xhtml"
    xmlns:m="http://jboss.com/products/seam/mail"
    xmlns:ui="http://java.sun.com/jsf/facelets">
      <m:from name="Open 18 Notifications" address="[email protected]"/>
      <m:replyTo address="#{currentGolfer.emailAddress}"/>
      <m:to>#{recipient.emailAddress}</m:to>
      <m:subject>#{currentGolfer.name} sent you a scorecard</m:subject>
      <m:attachment fileName="scorecard.pdf" contentType="application/pdf">
        <ui:include src="/exportScorecard.xhtml"/>
      </m:attachment>
      <m:body type="plain">While browsing the Open 18 course directory,
    I came across a golf course that I thought might interest you.

    #{course.name}

    The scorecard for this course is attached to this message.

    Cheers,

    #{currentGolfer.name}</m:body>
    </m:message>

This Facelets template provides a glimpse at using Facelets compositions to construct a message. The component named recipient is used to capture the target email address from a JSF form. The sendScorecard() method of the Notifications component preloads the scorecard data and then renders the email template. When that happens, the scorecard PDF is rendered and attached to the message. As a courtesy, the user is informed that the email went through. This method can’t be asynchronous in Seam 2.0 because the email template uses nonemail JSF component tags.

  @Name("notifications")
  public class Notifications {
      @In private Recipient recipient;
      @In private Renderer renderer;
      @In private FacesMessages facesMessages;
      @In(create = true) private Scorecard scorecard;

      public void sendScorecard() {
          scorecard.load();
          renderer.render("/email/scorecard-notification.xhtml");
          facesMessages.add(
            "The scorecard has been sent to #{recipient.firstName}.");
      }
  }

The chain reaction of one Facelets template invoking the next is a powerful concept. Table 13.4 provides a list of other common email tasks and how they can be accomplished.

Table 13.4. Solutions to common email composition tasks

Goal

How to achieve...

Conditional logic Use the rendered attribute on a component tag to toggle a single component or a grouping of components.
Email templates Design a composition template that uses <ui:insert> placeholders within an <m:message> region; call on this template from a content template and fill in the placeholders using <ui:define> and <ui:param>.
Send multiple messages Nest the <m:message> tag in an iteration component (i.e., <ui:repeat>).
Send to multiple recipients Nest the <m:to>, <m:cc>, or <m:cc> tag in an iteration component.
Customize the language or theme of a message Use the resource bundle map messages or theme to insert the value of a message key for the current locale or theme in the message; set charset using the charset attribute on the <m:message> tag.

I’d like to be able to say that you don’t have to lift another finger to send emails with Seam. Sadly, the task of configuring an email transport is a necessary evil. I can assure you that Seam makes this task just about as simple as it can be.

13.4.3. Setting up JavaMail in Seam

Setting up email for a project tends to be one of those black magic tasks that you do on the first day of employment and dare not touch again. Even then, someone is usually dictating the email settings over your shoulder, so it’s not much of a learning experience. In this section, I give you a clear understanding of what you need to do to configure a mail session.

To start, you need Seam’s mail module, jboss-seam-mail.jar, and the JavaMail API and implementation on the classpath of your application. The latter requirement is satisfied by the mail.jar and activation.jar libraries, both present in the lib directory of a seam-gen project. But guess what? You don’t need them if you’re deploying to a Java EE–compliant application server because they’re already provided. If you’re deploying to a servlet container, on the other hand, you need to bundle these libraries in your application or place them in the servlet container’s classpath. Let’s see how to use JavaMail in Seam.

Hooking up JavaMail to a transport

Seam provides a built-in component named mailSession that initializes and provides access to a JavaMail session (javax.mail.Session). But setting up the mail session is only half the story. The mail session is just a mediator between the application and the Mail Transport Agent (MTA). While the mail session negotiates with the MTA to send an email over the SMTP protocol, in the end it’s the MTA that actually sends the message. Thus, to configure a mail session, you must have access to an SMTP server.

Typically, the SMTP server is provided by your internet service provider (ISP) or your company. In Seam, you have two options for connecting a JavaMail session to an SMTP server. You can configure the connection information directly in the Seam component descriptor, or you can point Seam at a JavaMail session bound to JNDI. Seam also ships with an embedded mail server called Meldware that you deploy to JBoss AS, which is especially useful for development. Meldware can be controlled within the application using another set of built-in Seam components. You’ll find a step-by-step tutorial for configuring Meldware in the Seam reference documentation. The focus here is on using an externally hosted SMTP server because configuring Seam to use it is straightforward and it gets your emails out the door with the least amount of effort.

 

Save time by using an externally hosted SMTP server

To avoid time messing around with an email transport, your best bet is to take advantage of the wide array of free SMTP mail servers available on the web. One such example is Gmail, the example that is used in this section. Google allows messages to be sent over SMTP/TLS after proper authentication.[3] First, you must enable either POP or IMAP access on the Gmail account to use the Gmail SMTP server, and then configure Seam to use it.

3 See configuration instructions for Gmail: http://mail.google.com/support/bin/answer.py?answer=78799.

 

Configuring a Seam-managed JavaMail session

Although you never interact with the mail session component directly, it must be configured in order for the messages rendered by the message templates covered earlier to be sent. The mail session component is configured in the component descriptor just like many of the other Seam integrations, such as persistence. To start, add the component namespace http://jboss.com/products/seam/mail, typically prefixed as mail, to the component descriptor. Next, supply the connection information to the mailSession component. Here’s an example configuration that uses Gmail’s SMTP/TLS server.

  <mail:mail-session host="smtp.gmail.com" port="587"
    username="[email protected]" password="secret"/>

Of course, you need to fill in the correct username and password values for your account. The messages originate from the account’s email address. If the messages aren’t being sent, enable the debug property to diagnose the problem. Note that Gmail requires the tls property to be true, which is the default value. Placing connection information directly in the component descriptor isn’t very secure, nor can the values be customized for different environments. I recommend using replacement tokens, which were covered in chapter 5. An even more elegant approach, though, is to use an externally configured JavaMail session.

Configuring Seam to use a JavaMail session from JNDI

The mail session component can consume a JavaMail session stored in JNDI. In this section, I demonstrate how to configure the mail service in JBoss AS to bind a JavaMail session to JNDI, and show you how to configure the mail session component to use it. If you’re using an alternate application server, you can configure a JavaMail session using the server’s admin console. For GlassFish, see the note about SMTP authentication in the accompanying sidebar.

 

SMTP authentication and GlassFish JavaMail sessions

SMTP authentication is an automated login that occurs prior to sending an email message. If your ISP uses SMTP authentication, the JavaMail session fed to the Seam mail component must be configured to use an SMTP authenticator with the proper credentials already set. Unfortunately, it’s not possible to set the SMTP authentication credentials for a JavaMail session configured through GlassFish. JBoss AS, on the other hand, accommodates this configuration.

 

To register a JavaMail session in JBoss AS, open the mail-service.xml descriptor in the server’s hot deploy directory and replace the contents with listing 13.7.

Listing 13.7. Mail service configuration for JBoss AS
  <?xml version="1.0" encoding="UTF-8"?>
  <server>
    <mbean code="org.jboss.mail.MailService" name="jboss:service=Mail">
      <attribute name="JNDIName">java:/Mail</attribute>
      <attribute name="User">[email protected]</attribute>
      <attribute name="Password">secret</attribute>
      <attribute name="Configuration">
        <configuration>
          <property name="mail.transport.protocol" value="smtp"/>
          <property name="mail.smtp.host" value="smtp.gmail.com"/>
          <property name="mail.smtp.port" value="587"/>
          <property name="mail.smtp.auth" value="true"/>
          <property name="mail.smtp.starttls.enable" value="true"/>
        </configuration>
      </attribute>
      <depends>jboss:service=Naming</depends>
    </mbean>
  </server>

Next, supply the Seam mail component with the JNDI name assigned in this service:

  <mail:mail-session session-jndi-name="java:/Mail"/>

Restart your application and you should be able to send mail using Gmail. If you need to use a different SMTP server, simply fill in the appropriate values in the mail service configuration.

The JNDI name in this example uses the proprietary java:/ namespace for JBoss AS. The standard JNDI subcontext for JavaMail sessions is java:comp/env/mail. If you configured a JavaMail session named mail/Session in a Java EE–compliant server like GlassFish, you feed the value java:comp/env/mail/Session to the Seam mail component. You also have to declare a JNDI resource reference of type javax.mail.Session in web.xml.

You now have the full range of multipart email capabilities at your fingertips, sent into the great wide open using your ISP’s SMTP server. Just remember to use this power wisely, for fools are called spammers!

While email may not go away anytime soon, the email is mostly dead. Instead, people opt to keep up with the latest news using a newsfeed reader. That way, when you want the news to stop, all you have to do is unsubscribe (and it actually works!). At the risk of sounding mundane, creating newsfeeds in Seam is yet another Facelets template.

13.4.4. Publishing newsfeeds

What better way to produce XML than with XML. Don’t worry, I’m not talking about that scary XSLT pseudolanguage. I’m talking about Facelets. Seam does it again by making it extremely simple to publish newsfeeds, such as RSS or Atom, using a Facelets template. I’ll prove to you how simple it is by making this second extremely short.

Newsfeeds are delivered using XML, each type having its own schema. In this section, we work with an Atom feed. The only trick in serving XML through a Facelets template is setting the content type header appropriately. By default, Facelets assumes you’re generating HTML (text/html). But in order for the feed readers to digest the feed, the header must be an XML type. For an Atom feed, that type is application/xml+atom. You set the content type on the <f:view> tag, which can be placed anywhere in the document. As for the remainder of the document, you simply use the XML tags specific to the feed type. Facelets doesn’t care what markup it’s producing.

Let’s publish the latest golf rounds that have been entered by the golfers, a list provided by the context variable latestRounds. From that list, we create the feed template named latestRounds.xhtml, shown in listing 13.8.

Listing 13.8. An atom feed reporting scores from the latest rounds
  <?xml version="1.0" encoding="UTF-8"?>
  <feed xmlns="http://purl.org/atom/ns#" version="0.3" xml:lang="en"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:s="http://jboss.com/products/seam/taglib">
    <f:view contentType="application/atom+xml">
    <title>Open 18: Latest Rounds</title>
    <link rel="alternate" type="text/html"
      href="http://localhost:8080/open18"/>
    <tagline>A place for golfers</tagline>
    <updated><h:outputText value="#{latestRounds[0].date}">
        <s:convertDatetime pattern="yyyy-MM-dd'T'HH:mm:ss'Z'"/>
      </h:outputText></updated>
    <ui:repeat var="_round" value="#{latestRounds}">
    <entry>
      <title>#{_round.golfer.name} @ #{_round.teeSet.course.name}</title>
      <link rel="alternate" type="text/html"
        href="http://localhost:8080/open18/Round.seam?roundId=#{_round.id}"/>
      <id>http://localhost:8080/open18/Round.seam?roundId=#{_round.id}</id>
      <summary type="text/plain">#{_round.totalScore}</summary>
      <published><h:outputText value="#{_round.date}">
          <s:convertDatetime pattern="yyyy-MM-dd'T'HH:mm:ss'Z'"/>
        </h:outputText></published>
      <updated><h:outputText value="#{_round.date}">
        <s:convertDatetime pattern="yyyy-MM-dd'T'HH:mm:ss'Z'"/>
      </h:outputText></updated>
  </entry>
  </ui:repeat>
  </f:view>
</feed>

Despite all the fancy Java-based newsfeed creators, nothing beats writing the newsfeed in its native tongue, with some enhancements provided by JSF UI components to iterate over and format the data. The user simply requests the path /latestRounds.seam in the browser or feed reader to monitor the latest scores entered.

Throughout this chapter, you’ve added a lot of wealth to the application. But the richest applications are those that allow users to customize the UI to suit their needs. Seam provides a means of empowering your users to control internationalization, time zone, and theme settings for their sessions, with intelligent defaults to start.

13.5. Customizing the UI with resource bundles

Seam follows a consistent approach for providing customization of the UI by using resource bundles. As you learned in section 5.5.2 in chapter 5, Seam aggregates the i18n message bundles under a unified map named messages. When the application logic needs a message to be rendered, it supplies a message key rather than embedding the message string directly in the code. Seam then retrieves the actual message at runtime from the resource bundle, taking note of the user’s preference, whether it be a locale, time zone, or theme. Seam assembles a separate map named theme to support the active theme, prepared using a similar configuration (which is covered later in this section).

While chapter 5 examines the details of how to prepare a message bundle and use it in your components and pages, what you haven’t learned yet is how Seam decides which locale, time zone, and theme to use and how to give the user control over these selections. This section covers the three selector components that Seam provides, which are controlled by the UI, and also goes into detail about what themes are and how they fit into the resource bundle picture. Let’s begin by telling Seam how to speak the right language.

13.5.1. Getting Seam to speak the right language

In Java, a regional language is known as a locale. Locale selection has long been a standard part of the communication between the browser and the Java Servlet API, and is equally well supported in JSF. The negotiation works as follows. The browser sends a header named Accept-Language as part of the request, itemizing the languages understood by the user, weighted by preference. The server then sets the preferred locale accordingly, falling back to the server’s default locale if the Accept-Language header is empty or absent.

Granted, the user’s preferred locale setting doesn’t do much good if the application doesn’t support it. Thus, JSF goes a step further by comparing the user’s preferred languages against a list of locales the application claims to support and selecting the best possible match. If there are no matches, the server’s default locale is chosen. While Seam aggregates the i18n resource bundles in your application, it relies on JSF to handle the negotiation of the user’s locale.

You declare the supported locales and override the server’s default locale using the <locale-config> element in the JSF configuration file, /WEB-INF/faces-config.xml:

  <faces-config>
    <application>
      <locale-config>
        <default-locale>en</default-locale>
        <supported-locale>en</supported-locale>
        <supported-locale>fr</supported-locale>
      </locale-config>
    </application>
  </faces-config>

Of course, if you indicate that you support a locale, make sure you have a localized resource bundle to support it. Otherwise, the user is going to see the message key. To prevent the message key from being displayed, you can set fallback messages in the base bundle file (the bundle name followed immediately by the .properties extension).

That covers the standard language negotiation. Seam also provides built-in support for allowing the user to choose the effective locale from within the application (as opposed to changing the browser setting). This feature is particularly useful for internet terminals where the user can’t modify the browser’s setting. In that case, the Accept-Language header may not reflect the user’s true language preference.

Letting the user select the locale

Seam makes it simple to throw together a UI selector that controls the locale associated with the user’s session. The user’s locale is stored in a built-in component named localeSelector, which Seam consults whenever it needs to look up a message key. This component also has action methods that can be called to change the locale.

The typical way to use the localeSelector component is to bind its localeString property to a UISelectOne component and supply a list of locale keys in the options. The user can select an option to change the value of this property and in turn the effective locale. To apply the change, you bind the action of the form to the component’s select() method:

  <h:form id="settings">
    <span>Language: </span>
    <h:selectOneMenu value="#{localeSelector.localeString}">
      <f:selectItem itemValue="en" itemLabel="English"/>
      <f:selectItem itemValue="fr" itemLabel="Francais"/>
    </h:selectOneMenu>
    <h:commandLink action="#{localeSelector.select}" value="[ Select ]"/>
  </h:form>

You can do away with the UI command link by having JavaScript submit the form when a new option is selected. The selection is applied on the server using a value change listener:

  <h:form id="settings">
    <span>Language: </span>
    <h:selectOneMenu value="#{localeSelector.localeString}"
      valueChangeListener="#{localeSelector.select}" onchange="submit()">
      <f:selectItem itemValue="en" itemLabel="English"/>
      <f:selectItem itemValue="fr" itemLabel="Francais"/>
    </h:selectOneMenu>
  </h:form>

As you can see, I’m having to manually input the language choices in the select menu. But we’ve already supplied the supported locales once in the JSF configuration. Thankfully, Seam can consult the JSF context and prepare a ready-made list of SelectItem objects holding the locale strings and labels for use in the options of a UISelectOne component:

  <f:selectItems value="#{localeSelector.supportedLocales}"/>

Instead of using a select menu, you can iterate over this list to create links that select a locale, passing the locale string to a parameterized action method:

  <ui:repeat var="_locale" value="#{localeSelector.supportedLocales}">
    <s:link action="#{localeSelector.selectLanguage(_locale.value)}"/>
  </ui:repeat>

Seam’s default behavior is to store the selected locale for the duration of the user’s session. To make the selection more long term, it can be persisted as a cookie.

Getting the locale selection to stick

You can configure Seam to persist the locale setting in a browser cookie. First, add the component namespace http://jboss.com/products/seam/international, prefixed as i18n, to the component descriptor. Next, configure the localeSelector component to store the locale using a cookie:

  <i18n:locale-selector cookie-enabled="true"/>

The default lifetime of the cookie is one year, which you can change in the cookie-maxage attribute. You can also use this component to set the default locale, as with the JSF configuration file, but you can’t use it to specify the supported locales.

If you want to make the locale setting permanent, you can persist it to the database in a user preferences table. You then use the localeSelector in the authentication routine to transfer that setting to the user’s session:

  @In LocaleSelector localeSelector;

  localeSelector.setLocaleString(userPreferences.getLocale());
  localeSelector.select();

When the user changes the locale, Seam raises the org.jboss.seam.localeSelected event. You can observe this event to persist the selection back to the database:

  @Observer("org.jboss.seam.localeSelected")
  @Transactional
  public void localeChanged(String localeString) { ... }

Often forgotten about, but just as important as the language, is the time zone.

Managing the time zone

Seam offers parallel support for selecting a time zone using the timeZoneSelector. Here’s a UI component for switching time zones that mirrors the structure of the locale switcher:

  <span>Time zone: </span>
  <h:selectOneMenu value="#{timeZoneSelector.timeZoneId}"
    valueChangeListener="#{timeZoneSelector.select}" onchange="submit()">
    <f:selectItem itemValue="GMT-08:00" itemLabel="Pacific Time"/>
    <f:selectItem itemValue="GMT-07:00" itemLabel="Mountain Time"/>
    <f:selectItem itemValue="GMT-06:00" itemLabel="Central Time"/>
    <f:selectItem itemValue="GMT-05:00" itemLabel="Eastern Time"/>
  </h:selectOneMenu>

You can access the list of time zones known to the Java runtime from the timeZone context variable, which can be used to build the options for the time zone switcher:

  <s:selectItems var="_timeZoneId" value="#{timeZone.availableIDs}"
    label="#{timeZone.getTimeZone(_timeZoneId).displayName}"/>

Unfortunately, the result isn’t even close to being normalized. Your best bet is to retrieve the available time zones from a database. This list may be available in Seam in the future.

The time zone selector also has support for persisting the time zone selection in a cookie, having the same two property names as the localeSelector component:

  <i18n:time-zone-selector cookie-enabled="true"/>

When the time zone is changed, Seam raises the org.jboss.seam.timeZoneSelected event, passing the time zone id as an argument. Thus, the advice I gave earlier about storing the user’s locale preference in the database applies for the time zone as well.

Time zone repairs

Seam adds a bandage to another wound in JSF with regard to how time zones are used, or not used, for that matter. The JSF specification says that when using the <f:convertDateTime> converter, the date and time values should be assumed to be UTC (coordinated universal time) unless a time zone is explicitly specified in the tag. I can name at least two QA people who would strongly disagree that this is acceptable behavior. One way to override the default in Seam is to reference the timeZone context variable:

  <f:convertDateTime timeZone="#{timeZone}"/>

But rather than having to specify this override every time you use the converter, you can save a couple of keystrokes by using the <s:convertDateTime> converter from the Seam UI palette, which automatically applies the user’s time zone preference.

There’s another glitch in the way JSF handles time zones that Seam fixes. JSF doesn’t support setting a default time zone, using the time zone of the server instead. The default value can be customized using the timeZoneSelector as follows:

  <i18n:time-zone-selector time-zone-id="America/New_York"/>

Time zones are one of those things you need to synchronize from your database to your front end, so I advise that you spend some time thinking about and testing them.

While Seam certainly improves on the accessibility of locale and time zones, both for the developer and for the user, you’ve probably used these features before. Where Seam open new doors with resource bundles is in the area of themes—often referred to as skins.

13.5.2. Themes

Themes add another dimension to message bundles, as shown in figure 13.7. Just as locales allow you to switch between locale-specific variants of the same bundle name, themes allow you to switch between different bundle names that have the same set of key-value pairs. In the end, the idea of using a message key as a replacement token is still the same. The coolest part is that each theme can support multiple languages, providing i18n branding.

Figure 13.7. Themes add an extra dimension to resource bundle selection.

For each theme that you want to support, you must create a resource bundle whose name is that of the theme. For instance, to create a blue theme, you’d create the file blue.properties and place it on the application classpath (adjacent to your message bundle files). You’d then tell Seam about the themes by configuring the built-in Seam component named themeSelector in the component descriptor:

  <theme:theme-selector cookie-enabled="true" theme="blue">
    <theme:available-themes>
      <value>red</value>
      <value>green</value>
      <value>blue</value>
    </theme:available-themes>
  </theme:theme-selector>

Here, three themes have been defined, with blue being the default. You can allow the user to switch themes using the same approach that was used for locale and time zone selection:

  <span>Theme: </span>
  <h:selectOneMenu value="#{themeSelector.theme}"
    valueChangeListener="#{themeSelector.select}"
    onchange="submit()">
    <f:selectItems value="#{themeSelector.themes}"/>
  </h:selectOneMenu>

Without additional configuration, the bundle name of each theme is shown in the label of the options in the select menu. If you want to give the themes fancy names, you must add message keys in your Seam message bundle (e.g., messages.properties) that use the prefix org.jboss.seam.theme. followed by the theme name:

  org.jboss.seam.theme.blue=Sky
  org.jboss.seam.theme.green=Eco
  org.jboss.seam.theme.red=Ruby

Now that you have a theme, how do you use it? Well, just like any other message bundle. Only, rather than using a map named messages, you use a map named theme. You can tie a style sheet, logo, and master template to the theme using the following three message keys:

  stylesheet=#{request.contextPath}/stylesheet/sunnyday.css
  logo=noclouds.png
  template=layout/outside.xhtml

You can reference the first two keys in the master Facelets template:

  <link href="#{theme['stylesheet']}" rel="stylesheet" type="text/css"/>
  <h:graphicImage value="#{theme['logo']}" alt="Logo"/>

and then select the master template for the specific page using the last key:

  <ui:composition xmlns="http://www.w3.org/1999/xhtml" ...
    template="#{theme['template']}">
    ...
  </ui:composition>

If you want to tie the colors used in PDF documents and charts into your theme, you first define <p:color> tags that each have a semantic name and bind to a theme’s key:

  <p:color name="series1" value="#{theme['series1Color']}"/>

You then associate the semantic name with a color by assigning a value to the theme’s key:

  series1Color=#FF0000

If you want to skip creating the theme message keys, you can just reference the theme name directly in a value expression:

  <h:graphicImage value="#{themeSelector.theme}.png" alt="Logo"/>

Themes can be used to control just about anything that accepts an EL value expression. Check out the sample code to see how you can tie the theme to a RichFaces skin. I have no doubt you’ll think of other creative ways to use these special resource bundles.

13.6. Summary

I hope the promise I made that you’d have fun in this chapter held true. Looking back on what you’ve learned, you can now handle file uploads, render dynamic images, generate PDFs, create charts, compose emails with attachments, publish news feeds, and customize the UI with resource bundles. These features can take your application from good to great.

The constant theme throughout this chapter has been ease and accessibility. Software development is rarely easy. But it doesn’t have to be unnecessarily difficult either. Many of the areas of functionality covered in this chapter have been huge pain points for Java developers in the past, particularly file uploads and multipart email messages. Seam’s UI component tags make these problems just melt away.

Seam is able to accomplish this feat in two ways. First, Seam extends the EL value binding concept to transport binary data in addition to strings. Going the other way, Seam renders raw file data as a dynamic graphic in a web page, in a PDF document, or as a file attachment in an email. The other key is the Facelets template. Facelets is powerful because it is a stand-alone rendering technology, but with access to all the power of JSF components. One way to leverage this tool is to render a Facelets template within an action method to send an email. Another is to serve a Facelets template that generates a PDF to the browser, allowing the user to download the result. Yet another is to publish a newsfeed. If the Facelets template is being served directly to the browser, you can even take advantage of Seam’s page-oriented controls. The focus is to give you the same XHTML-based approach backed by the EL and Seam components no matter what format is being produced.

That brings this book to a close, but not your journey with Seam. There are additional chapters online to broaden your Seam knowledge. Chapter 14 takes you into the world of business processes and shows how they follow the same declarative approach as conversations. Chapter 15 details how to integrate Seam and Spring using Seam’s Inversion of Control (IoC) bridge. For those of you who won’t pick up another framework if it means letting go of Spring, that chapter should definitely be of interest.

Before you close the book, I want to say that I hope both Seam and this book change your life as they have for me. Thanks for reading and good luck with your next application!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.12.34