Chapter 3. Seam Page Flow

In the previous chapter, we looked at the components necessary to build a simple Seam application. The simple application that we developed supported only one JSP page. However, in the real world, this is far from sufficient! Within Seam applications, there are several different ways to navigate through web pages. We will discuss these ways in this chapter, including:

  • Simple navigation

  • JSF style navigation

  • Seam jPDL navigation

Simple navigation

In the previous chapter, we saw that performing business logic and redirecting a user to a different web page can be performed by adding a commandButton to a web page. The action property of a commandButton specifies the business method to be executed, and this method is then responsible for routing the user to a web page. If we want to route the user back to the same page they were viewing before clicking on the actionButton, the business method simply returns an empty string, as shown in the following code snippet.

<h:commandButton action="#{manager.run}" value="Run" />
@Name("manager")
…
public String run() {
return "";
}

This is the simplest possible case—returning the user to the same point at which they started. Although this type of page routing can be very useful, it is highly limited. It is much more useful to redirect a user to a different page after performing some business action.

Note

In Seam, it is not necessary to return a value from a method to get the initial page re-rendered. Declaring an action as void will have the same effect as returning an empty string.

@name("manager")

public void run() {

}

With simple navigation, the user can be redirected to a different page by returning the path to the page from the business method, as shown in the following example.

<h:commandButton action="#{manager.run}" value="Run" />
@Name("manager")
…
public String run() {
return "/results.jsp";
}

In this example, the user clicks on the Run button, which causes the run() method to be executed with the browser page open, then the user is redirected to /results.jsp (which will in turn be routed to /results.seam as per our JSF mapping).

In smaller applications, it may be completely appropriate to use this style of page routing throughout the entire application. However, this can get cumbersome as the number of pages and business methods gets larger. There is high coupling between the business logic and the application page flow because the page flow is effectively hard-coded into the application. In this scenario, if we wanted to show a summary results page before the full results, we would need to change the business method rather than simply change the page flow.

Seam style navigation

Seam style page navigation eliminates the need for hard-coding page flows into the application's business logic. Typically in JSF applications, page flows are defined in the faces-config.xml file. This JSF page flow is defined by a set of navigation rules, which can either be specified on a per-page basis (if the <from-view-id /> element is defined), or on a global basis.

<! This navigation rule defines page flow from /myPage.jsp -->
<navigation-rule>
<from-view-id>/myPage.jsp</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/success.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>fail</from-outcome>
<to-view-id>/failure.jsp</to-view-id>
</navigation-case>
</navigation-rule>
<! This navigaion rule defines global page flow -->
<navigation-rule>
<navigation-case>
<from-outcome>logout</from-outcome>
<to-view-id>/logout.jsp</to-view-id>
</navigation-case>
</navigation-rule>

Seam applications use a similar mechanism for performing page flow. However, they are defined within a pages.xml file, which is stored in the /WEB-INF folder of a web application. It is possible to combine the two styles (JSF and Seam) by defining page flows in both, the faces-config.xml file as well as the pages.xml file. However, in larger applications, this will soon get unwieldy and difficult to support. It is recommended that page flow logic be stored in the pages.xml file to ensure that the page flow logic is defined in one central place.

Defining page flows in pages.xml rather than faces-config.xml has several advantages:

  • pages.xml allows parameters to be passed within page flows

  • We can use JSP expression language within pages.xml file

If you have many page flows within an application, the pages.xml file can get very large. Seam allows page flows to be defined on a per-page basis, or on the basis of all pages in a central file. In the per-page basis case, page flow files must be stored in the same location as the JSP file and must follow the standard naming syntax, which is <jspfilename>.page.xml. The following are all examples of page flow files:

  • WEB-INFfaces-config.xml

  • WEB-INFpages.xml

  • vacations.page.xml (assuming we have a vacations.jsp file)

Defining a page flow in pages.xml

A page flow in Seam is typically defined by specifying one or more navigation rules within a page tag. Navigation rules can be specified either for a single page, or for all pages by using a wildcard as the page name. A basic navigation rule would be defined as in the following code sample:

<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd">
<page view-id="/vacations.jsp">
<navigation from-action= "#{vacationManagerAction.selectVacationType}">
<rule if-outcome="city">
<redirect view-id="/city.jsp" />
</rule>
</navigation>
</page>
</pages>

In this sample, we can see that the pages.xml file is defined with a root element called <pages> using the appropriate name spaces. For each page, a <page> element is defined that holds multiple <navigation> definitions. Each of these navigation elements defines navigation rules for a particular page flow action. Each navigation rule is defined by a <rule> element. Let's take a look at each of these as they are defined in this example.

<page> element

The <page> element defines the pages to which the enclosed navigation rules are applicable. In the previous example, the enclosed navigation rules are applicable to the /vacations.jsp JSF page. If we wanted to create a set of navigation rules applicable to a set of pages, we could specify a wildcard as the view-id, for example:

<page view-id="/*">
<page view-id="/secure/*">

<navigation> element

The <navigation> element is used in JSF expression language to define which business method on a Seam component is responsible for initiating our page flow. In the previous example, the public String selectVacationType() method is on a Seam component named vacationManagerAction.

<rule> element

Individual navigation rules are defined within <rule> elements. In its simplest form, a navigation rule can redirect to a different JSF page based on the outcome of the business method defined in the <navigation> element.

<rule if-outcome="city">
<redirect view-id="/city.jsp" />
</rule>

In this example, if the business method on the Seam component returns the string city, the /city.jsp page will be displayed.

You'll remember that in the previous section, we stated that we could use a JSP expression language in Seam page flows. Instead of using the if-outcome attribute of rules, we can use the if attribute to decide the page flow based upon logical decisions made in the pages.xml file.

<rule if="#{destination.minimumBudget lt 100.0}">
<redirect view-id="/insufficientfunds.jsp" />
</rule>

In this example, we have created a rule that specifies that if the minimumBudget property on the destination Seam component is less than 100.0, redirect the page to the /insufficientfunds.jsp web page.

This technique of allowing us to use expression language in page flows is very powerful, and one of the reasons why Seam page flow is more powerful than the typically provided page flow in a JSF application.

Navigation rules are executed in the order that they are defined within the pages.xml file. This allows certain navigation rules to take precedence over others, depending upon their ordering within the file.

Error handling

It is inevitable that exceptions are thrown within the applications that we write. Seam provides functionality to allow exceptions thrown within Seam components to be caught and handled gracefully within the web user interface. To catch unhandled exceptions thrown from Seam components, we need to add an <exception> tag into the pages.xml file. This tag allows us to identify which exceptions are to be handled via the class attribute, and which web page is to be displayed when the error is caught. Within the <exception> tag, we can also add an error message by using the <message> tag. These messages can be displayed on the resulting error page, to provide additional information to the user, showing what has gone wrong.

<exception class="com.davidsalter.HolidayException">
<redirect view-id="/error.jsp">
<message severity="ERROR">
Whoops. Better make sure we write some better tests !
</message>
</redirect>
</exception>

In this code example, if an exception of the type com.davidsalter.HolidayException is thrown, the user will be redirected to the /error.jsp page with an appropriate message being added to the JSF messages collection.

In order to configure Seam applications to use this type of exception handling, we first need to configure the org.jboss.seam.servlet.SeamFilter within the application's web.xml file. This is a one-off process that needs to be done for each web application on which we intend to use this type of exception handling. To configure the web.xml file, the following code needs to be added to the file:

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

Executing code before rendering pages

By using the pages.xml file, it is possible to define methods within Seam components that can be executed before a page is rendered. This is achieved by specifying the action attribute on a <page> element.

<page view-id="/vacations.jsp"
action="#{vacationManagerAction.beforeRender}">
</page>

In this example, the beforeRender() method on the vacationManagerAction Seam component will be invoked before the /vacations.jsp page is rendered.

If we want to execute multiple actions before a page is rendered, we can specify the actions by using the <action /> element rather than the action attribute of the <page /> element.

<page view-id="/vacations.jsp">
<action execute="#{vacationManagerAction.beforeRender}" />
<action execute="#{vacationManagerAction.doMore}" />
</page>

The <action /> element gives us an additional advantage: it lets us define conditional page actions. Page actions can be made conditional by specifying a piece of EL within the if attribute of the <action /> element. The action is then performed only if the if attribute evaluates to true.

<page view-id="/vacations.jsp">
<action execute="#{vacationManagerAction.beforeRender}" if="#{vacation.debug} />
<action execute="#{vacationManagerAction.doMore}" if="#{not vacation.debug} />
</page>

A working example

In the previous few sections, we've looked at some of the theory behind page flow navigation within Seam applications. Let's now pull all of this together in a working example.

Note

The complete code base for this sample application can be found within the Chapter 3/Vacation Planner folder of the code download bundle for the book.

To gain practice with the Seam page flow, our sample application displays a set of vacation types and allows the users to select their favorite type of vacation. In addition, the user can select their minimum budget. The sample application appears as shown in the following screenshots:

A working example

Once you have gone through all of the pages and made your choices, you will be redirected to the following page:

A working example

In this sample application, we have different types of page flows being used.

  • If the user selects an Adventure vacation as his or her favorite, he or she is reminded to get travel insurance.

  • If the user selects a Beach vacation as his or her favorite, he or she is reminded to take plenty of sun block.

  • If the user selects a City break as his or her favorite vacation, he or she is reminded not to tire themselves out by shopping.

  • Finally, if the user has a budget of less than $100, he or she is told they have insufficient funds and cannot afford to go on vacation. This navigation rule overrides all other rules.

First of all, we need to define a Seam component that we will be using to manage all the page flows for our sample application. We will use a stateful Session Bean for this functionality. We have chosen a stateful Session Bean for the Seam manager component because, in the real world, we would be performing database queries and maintaining state across different web pages. As we want to maintain state throughout our application, a stateful Session Bean makes sense. The code for this Session Bean is as follows:

package com.davidsalter.vacationplanner.action;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.persistence.PersistenceException;
import com.davidsalter.vacationplanner.model.Destination;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.log.Log;
@Name("vacationManagerAction")
@Stateful
public class VacationManagerAction implements VacationManager {
@In
Destination destination;
@Logger
private Log log;
public String selectVacationType() throws Exception {
log.debug("DestinationType: ", destination.getDestinationType());
if (destination.getDestinationType() == Destination. DestinationType.ACTION)
return "danger";
else if (destination.getDestinationType() == Destination. DestinationType.BEACH)
return "beach";
else if (destination.getDestinationType() == Destination. DestinationType.CITY)
return "/city.jsp";
else
throw new Exception("Oops");
}
sample application, Seam page flowSeam components, definingpublic void beforeRender() {
log.info("Just about to render the page.");
}
@Remove
@Destroy
public void remove() {
}
}

We can see that this class is a simple POJO, similar to the Seam components that we looked at in the previous chapter. This class does not implement any Seam-specific interfaces or extend any Seam-specific classes. The @Name annotation defines the name of this Seam component as vacationManagerAction. The following action that is within this class is executed when the user clicks on the Select> button on our web page:

public String selectVacationType() throws Exception {}

Within this method is a rather large if .. else block that is responsible for initiating different page flows. The following outcomes are possible from this block:

User Selection

Outcome

User selects Action

The navigation rule, danger, will be executed.

User selects Beach

The navigation rule, beach, will be executed.

User selects City

The user will be forwarded to the /city.jsp page.

The user makes no selection

An exception is thrown, which will be picked up and handled by the Seam exception handling page specified in the pages.xml file.

The vacations.jsp JSF page invokes this action; it is as follows:

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
<html>
<head>
<title>Vacation</title>
</head>
<body>
<f:view>
<h:form>
<p>What type of holiday do you like?</p>
<h:selectOneMenu
value="#{destination.destinationType}">
<s:selectItems
value="#{destinationTypes}" var="type"
label="#{type.label}"
noSelectionLabel="Please select" />
<s:convertEnum />
</h:selectOneMenu>
<p>What is your minimum budget?</p>
<h:inputText
value="#{destination.minimumBudget}" />
<br/>
<h:commandButton
action =
"#{vacationManagerAction.selectVacationType}"
value="Select>" />
</h:form>
</f:view>
</body>
</html>

<s:selectItems/> and <s:convertEnum />

This code is very similar to what we have seen previously, except that we have introduced the <s:selectItems /> and <s:convertEnum> tags within the standard JSF <h:selectOneMenu> tag.

Within a JSF page, we typically use the <h:selectOneMenu /> tag to display an HTML drop-down menu. This tag renders an HTML <select /> tag, as shown:

<select>
<option value="ACTION">Action</option>
<option value="BEACH">Beach</option>
<option value="CITY">City</option>
</select>

Within static web applications, this list (vacation types, in our example) can be hard-coded into the HTML page. However, with a dynamic web application, we want to get this list from a database or from the members of an enumeration. The Seam <s:selectItems /> tag allows us render a dynamic List, Set, DataModel, or Array as HTML code.

The <s:selectItems /> tag supports several attributes, as shown here:

<s:selectItems
value="#{destinationTypes}" var="type"
label="#{type.label}"
noSelectionLabel="Please select" />
<s:convertEnum />

Attributes

Explanation

disabled

Boolean flag indicating whether the items are rendered or not.

hideNoSelectionLabel

Boolean flag indicating whether the noSelectionLabel is hidden.

label

The value that is displayed to the user as an item to be selected.

noSelectionLabel

Optional item to be placed at the top of the list to indicate that no selection has been made. Typically, this would contain a text such as Please select.

value

JSF EL that specifies the Seam component data to be displayed in the drop-down list box. This can either reference a List, a Set, a DataModel, or an Array.

var

Local variables that hold the current item being rendered. This can be used, for example, to change the label depending on the item being rendered.

Within a JSF component model, the JSF runtime converts data types that are held as backing beans or Seam components into the relevant format to allow them to be displayed on an HTML page. Seam provides standard converters to allow different component types to be rendered within drop-down lists on a web page. The <s:convertEnum /> converter allows us to render enumerations as data lists on HTML forms—in our example, we are rendering an enumeration into a drop-down selection list.

To tell the JSF runtime that we wish to use a converter, we must declare the converter alongside the appropriate <s:selectItems /> tag, and within the <h:selectOneMenu /> tag, as shown below. The <s:convertEnum /> tag contains no attributes.

<h:selectOneMenu …>
<s:selectItems … />
<s:convertEnum />
</h:selectOneMenu>

Note

Check out Sun's JSF technology page for more details about JSF converters http://java.sun.com/javaee/javaserverfaces/.

Rendering the options in the sample application

As we have only a finite number of options to display to the user in our sample application (Action, Beach, and City), we're using an enumeration to list all of the options. In a larger application, these values would probably be pulled from a database (we'll look at how that is done in a future chapter). Each destination type is defined as a Java enumeration. This enumeration is held within a Java class, namely Destination, which also holds the minimum amount that a user of our application can spend on his or her vacation.

package com.davidsalter.vacationplanner.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.Version;
import org.jboss.seam.annotations.Name;
@Name("destination")
public class Destination implements Serializable {
public enum DestinationType {
ACTION("Action"), BEACH("Beach"), CITY("City");
private String label;
DestinationType(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
private double minimumBudget;
@Enumerated(EnumType.STRING)
private DestinationType destinationType;
public DestinationType getDestinationType() {
return destinationType;
}
public void setDestinationType(DestinationType type) {
this.destinationType = type;
}
public double getMinimumBudget() {
return minimumBudget;
}
public void setMinimumBudget(double minimumBudget) {
this.minimumBudget = minimumBudget;
}
}

The enumeration has a property called label. This is used for displaying the name of the destination type within the web page. The enumeration property that is displayed within the web page is configured via the label attribute of the <s:selectItems> tag.

Note that this enumeration isn't explicitly defined as a Seam component, as it isn't directly referenced within the JSF page. However, the value of the selected destination type is stored within the destination.destinationType Seam component. In this instance, the Destination class is defined as a Seam component called destination.

Within our manager class, namely VacationManagerAction, we are injecting the Seam component destination. This allows data entered on the web form to be passed to our business logic Seam component. So far though, we have not seen how the drop-down list gets populated. From the code fragment, we can see that the drop-down list is bound to a Seam component called destinationTypes, but we have not defined that yet.

The destinationTypes Seam component is a special component annotated with the @Factory annotation.

package com.davidsalter.vacationplanner.model;
import com.davidsalter.vacationplanner.model.Destination. DestinationType;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.Name;
@Name("factories")
public class Factories {
@Factory("destinationTypes")
public DestinationType[] getDestinationTypes() {
return DestinationType.values();
}
}

When the vacations.jsp web page is first loaded, Seam will try to resolve all of the required components to allow the page to be rendered correctly. If a component is not found, Seam will try to instantiate the component via its factory method, as defined by the @Factory annotation. In this example, the factory method for the destinationTypes component simply returns an array of the entries within our DestinationType enumeration.

We've now seen all of the Java and JSP code that make up our sample application. The final part to look at is the pages.xml file that defines the application's page flows.

<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd">
<page view-id="/vacations.jsp"
action="#{vacationManagerAction.beforeRender}">
</page>
<page view-id="/vacations.jsp">
<navigation from-action="#{vacationManagerAction. selectVacationType}">
<rule if="#{destination.minimumBudget lt 100.0}">
<redirect view-id="/insufficientfunds.jsp" />
</rule>
<rule if-outcome="danger">
<redirect view-id="/danger.jsp" />
</rule>
<rule if-outcome="beach">
<redirect view-id="/beach.jsp" />
</rule>
</navigation>
</page>
<exception>
<redirect view-id="/error.jsp">
<message severity="ERROR">
Whoops. Better make sure we write some better tests !
</message>
</redirect>
</exception>
</pages>

Note

In the navigation rules defined in the pages.xml file, we do not have a navigation rule for the case where the destination type is set as CITY. This is because we have declared this navigation rule explicitly within the VacationManagerAction class.

This has been added to show a comparison of the different navigation techniques that are available within a Seam application. Please remember that this tight coupling between business logic and presentation logic is not always a good idea.

This file defines the navigation rules for the /vacations.jsp web page when the vacationManagerAction.selectVacationType Seam component method is executed. We can see that there are three navigation rules and one exception rule defined within the file. You will remember that we previously mentioned that the navigation rules are executed in sequence from top to bottom. The first rule to be executed uses JSF expression language to check the user's minimum budget and display an appropriate page if the user has less than $100 to spend. If this rule is executed, then processing stops and the subsequent rules are not processed. In our application, therefore, if the user has insufficient funds, it doesn't matter what choices they make for the favorite vacation types, as this rule will always be executed first. Following this rule are the rules to display the appropriate pages depending on the user's selection on the web page.

Running the sample application

To run the application, we need to start up JBoss, copy the appropriate JAR files into the lib directory of the project (see the previous chapter for details), and then execute the ant deploy target. If all goes well, the application should be deployed to the JBoss Application Server and should be accessible at:

http://localhost:8080/VacationPlanner/vacations.seam

Seam jPDL navigation

In the previous section, we saw how it is possible to use Seam navigation rules to define page flows within an application. Seam also allows us to use the JBoss jBPM Process Definition Language (jPDL) to define page flows within an application. A full description of jBPM and jPDL is outside the scope of this book and other texts are available to describe it (see for example—Business Process Management with JBoss jBPM by Matt Cumberlidge).

Developing page flows with jBPM is probably more complex than with Seam style navigation rules. However, JBoss provides tools to help developers easily visualize jPDL page flows making them much easier to understand.

At the moment, it's sufficient to know that we can use jPDL to define our page flows.

Summary

In this chapter we've looked further at Seam, in particular at, page navigation using Seam page flows. We've seen how we can define page flow within the pages.xml file of our applications by defining both static rules and rules encompassing JSF expression language. We've taken our first steps into developing a vacation planning web site that we'll expand on in the future chapters.

We've also looked at Seam factories and how to display drop-down combo boxes within the web pages based upon Java enumerations.

So far, while learning Seam, we've concentrated entirely on using JSP as our view technology. Now that we have a good understanding of the key concepts behind Seam, we are going to investigate the Facelets view technology in the next chapter, where we'll see why JBoss prefers this over JSP.

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

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