</page>
Now let’s assume that you want to bring up the list of facilities that are in the home state of the user, if the user is authenticated. You’ll learn how to implement authenti-cation with Seam in chapter 11. Assuming that there’s a mechanism available to access the current user’s information, the #{facilityList.applyRegionalFilter} method will apply it to the search parameters, but only if an active search isn’t detected (to avoid interfering with it). The facilities will then be preloaded as before since the actions are executed in the order they appear in the page node. To apply multiple page actions to a single page node, you use nested <action> nodes:
<page view-id="/FacilityList.xhtml">
<action execute="#{facilityList.applyRegionalFilter}"
if="#{identity.loggedIn and !facilityList.searchActive}"/>
<action execute="#{facilityList.preloadFacilities}"/>
...
</page>
Having the ability to execute a method prior to rendering is only half the benefit. The true value of page actions is their ability to trigger declarative navigation. This is one feature you don’t get by putting the prerender logic in the beforePhase() method of a JSF PhaseListener.
3.4 Combining page actions with navigation
The navigation that follows a page action works just like the navigation used after the Invoke Application phase on a JSF postback. Thus, Seam’s page actions can be
combined with its intelligent navigation capabilities in order to make decisions about how to direct the user in the event that a page action needs to divert the user from the requested page. If you perform a redirect—not a <render>—in the naviga-tion rule, then the ensuing page may also use a page acnaviga-tion. Thus, chaining acnaviga-tions prior to rendering a page is possible. A view ID that participates in this chain does not need to correspond to an view template (i.e., it’s a pseudo-page).
The most obvious use for combining a page action with navigation is to validate that the URL being requested is legitimate and that the page can be rendered successfully.
3.4.1 Sanity checking a request
Consider what happens when a user requests the course detail screen directly, per-haps from a bookmark. The course is looked up by id using the value supplied in the courseId request parameter. What happens when the requested courseId is empty or no such id exists in the course table? JSF is notoriously awful at handling this situa-tion. Because the JSF controller works in a passive manner, it doesn’t figure out that the request is missing information until halfway through the rendering process. Once the page begins rendering, you can’t reroute the user to a more appropriate page, even when it becomes apparent that the target data is absent—unless you throw an exception. You end up displaying a page with blank values and other potential ren-dering glitches.
Page actions to the rescue! Let’s implement a method validateEntityFound() that verifies that a course can be found before rendering begins:
public String validateEntityFound() { try {
this.getInstance();
}
catch (EntityNotFoundException e) { return "invalid";
}
return this.isManaged() ? "valid" : "invalid";
}
Behind the scenes, the getInstance() method is using the courseId value that’s assigned to the courseHome component by the page parameter to look up the corre-sponding entity instance in the database. The isManaged() method tells us whether the entity was found in the database, as opposed to a new, transient instance being created.
Of course, if things don’t go well, and the outcome value is “invalid,” then we need to perform navigation. Navigation rules are invoked after executing a page action just as they are when an action is invoked on a JSF postback. Here, we redirect users to the /CourseList.xhtml JSF view if the course cannot be successfully loaded, letting them know with a warning message why they were redirected:
<page view-id="/Course.xhtml" action="#{courseHome.validateEntityFound}">
<navigation from-action="#{courseHome.validateEntityFound}">
<rule if-outcome="invalid">
<redirect view-id="/CourseList.xhtml">
<message severity="WARN">
The course you requested does not exist.
</message>
</redirect>
</rule>
</navigation>
</page>
Your Course.xhtml page is now protected from bogus requests. You may be wondering about the CourseEdit.xhtml page, which also needs to be protected. You could apply the same logic to that page as well by registering equivalent configuration with the /CourseEdit.xhtml view ID. However, just to demonstrate additional capabilities of the <page> node, let’s combine the two view IDs together and use a complex condi-tional expression to determine when the validation should be applied. First, a <page>
node is defined that matches all view IDs that begin with /Course. Then, by consulting the implicit JSF expression #{view.viewId}, which resolves to the current view ID, the validation can be applied to the detail page and, if the courseId property on course-Home is non-null, the editor page:
<page view-id="/Course*">
<action execute="#{courseHome.validateEntityFound}"
if="#{view.viewId == '/Course.xhtml' or (view.viewId == '/CourseEdit.xhtml' and courseHome.courseId != null)}"/>
<navigation from-action="#{courseHome.validateEntityFound}">
<rule if-outcome="invalid">
<redirect view-id="/CourseList.xhtml">
<message severity="WARN">
The course you requested does not exist.
</message>
</redirect>
</rule>
</navigation>
</page>
Note that the navigation rules are consulted after each action is executed. If the outcome of a page action matches a navigation rule, the remaining page actions will be short-circuited. So for all page actions to execute, only the last one can trig-ger navigation.
As you can see, it’s possible to create fairly sophisticated rules about when to invoke page actions. However, they do require some setup. To handle the more com-mon cases of prerender functionality, Seam provides a couple of built-in page actions to secure a view.
3.4.2 Built-in page actions
Every application needs certain page-oriented features, such as the ability to restrict unauthenticated or unauthorized users from accessing protected pages. Rather than forcing you to invest time in a solution for every application, Seam offers a handful of built-in page actions for handling this work.
If you’ve ever tried to use a servlet filter-based security mechanism, such as Spring Security, to secure JSF pages, you were likely frustrated by the fact that it doesn’t do a good job of securing JSF pages because it’s not granular enough. Securing JSF pages is easy if done at the proper level, just prior to the Render Response phase. Page actions are the perfect fit. To restrict access to users that aren’t authenticated, you simply add the login-required attribute to the page definition:
<page view-id="/CourseEdit.xhtml" login-required="true"/>
You can also enforce custom security rules using a nested <restrict> element. If you’re using a Java Authentication and Authorization Service (JAAS) principle (we cover its configuration in chapter 11), and you want to enforce role-based security according to the return value of isUserInRole(), then you can do so using the built-in EL function named s:hasRole in the <restrict> element. Let’s assume that there’s a role named
“edit” that is used to grant privileges to modify records. You can prevent anyone who isn’t assigned the edit role from modifying a course with the following page declaration:
<page view-id="/CourseEdit.xhtml" login-required="true">
<restrict>#{s:hasRole('edit')}</restrict>
</page>
In a fashion similar to how you require the user to be authenticated, you can enforce that a conversation already be in effect by adding the conversation-required attri-bute to the page declaration. Both of these tags can also be added to the root <pages>
node if you want either of them to apply to all pages. If either of these two conditions fails, Seam provides the login-view-id and no-conversation-view-id attributes that indicate where to direct the user. We’ll look at security and conversations in much greater depth later in this book. Just note that these are features of Seam that you can configure using pages.xml.
There is another built-in page action that is capable of loading a message bundle for a set of pages. The keys are added to the unified message bundle that’s prepared by Seam. You learn how to configure and use the Seam message bundle in section 5.5.2 of chapter 5. The following declaration loads the message bundle defined in the admin.properties file on the classpath for the administration section of the website:
<page view-id="/admin/*" bundle="admin"/>
It’s also possible to enforce that a page be requested via an HTTPS request using a built-in action. The scheme attribute on the page declaration checks the current scheme and redirects the user to the appropriate scheme if it’s not the correct one:
<page view-id="/secure/*" scheme="https"/>
I could fill a book trying to cover every last detail of Seam’s page-oriented functional-ity. Since there’s plenty more in Seam to cover, I need to move on rather than itemize the entire page descriptor schema. I encourage you to consult the Seam reference documentation if you’re curious about a lesser-used page descriptor configuration element that I haven’t covered here.
The prerender logic discussed so far is transparent to the user. However, a URL that makes sense to the developer doesn’t always make sense to the end user or a search engine. Specifically, URLs that have a lot of query string parameters appear rather cryp-tic. The next section shows you how to create friendlier-looking URLs in Seam.
3.4.3 Search engine–friendly URLs
Using a layer of abstraction between the URL and the rendered view can accommodate a more logical, prettier, and RESTful URL strategy. The goal is to turn URLs that look like /Course.seam?courseId=15 into /course/view/15. JSF wasn’t designed with the REST concept in mind, so we must look elsewhere. Although it’s possible to preprocess the request using page actions, as we did in the previous example, it’s far easier to accom-plish this task using a third-party rewrite filter, aptly named UrlRewriteFilter. If you’re familiar with Apache’s mod_rewrite filter, the premise is the same.
At the time of this writing, Seam doesn’t include a filter in its filter chain for enabling and configuring the UrlRewriteFilter, though it’s expected to be in the Seam 2.1 release. Until then, it needs to be configured in the web.xml descriptor. Add the fol-lowing XML stanza anywhere below the SeamFilter in that file:
Why are search engine–friendly URLs desirable?
To help people find information on your site, you want to ensure that search engines understand your website or application. One way to optimize a site for search engines is by making the links that point to other pages of the site self-describing. The URL should contain words and characters that provide clues as to what resource will be displayed when the URL is requested. Search engines can then learn this pattern and provide search results that associate a direct link to a resource that matches the search terms.
All the relevant information should also be in the URL path. Putting information in the query string makes it difficult for the search engine to sort between essential and nonessential information and may even cause important information about the resource to be truncated. Statistics engines are notorious for wreaking such havoc on URLs. So having search engine–friendly URLs also means having statistics engine–friendly URLs. The statistics generated are also more accurate since you get the granularity of showing the requests for resources rather than just the servlet path that serves them.
Search engine–friendly URLs are desirable because they are simple and they are technology agnostic. Suppose you implement your application in Struts and spread around links that ended in .do. Then, if you later switch to JSF, all of the existing links instantly become invalid, likely resulting in a 404 error for the user. You now have the job of publicizing the new links that end in .jsf (or .seam). Instead, what you want to do is make the URL about the resource, not about the framework that’s serving it.
<filter>
You’ll also need to modify the build to include the urlrewritefilter.jar file in the deployment archive. Please see appendix A for details on how to add a library to the deployment archive.
The rewrite rules are defined in the /WEB-INF/urlrewrite.xml descriptor. The rules are defined using either Perl 5 regular expressions or wildcards. It’s possible to capture match references and pass them on to the new URL that’s constructed. Listing 3.5 shows the configuration for friendly course URLs.
<?xml version="1.0" encoding="UTF-8"?>
<to last="true">/Course.seam?courseId=$1</to>
</rule>
<rule>
<from>^/course/edit/([0-9]+)$</from>
<to last="true">/CourseEdit.seam?courseId=$1</to>
</rule>
Friendly URLs can cause relative paths to break since the servlet path is no longer rep-resentive of the rendered view. A reference to a stylesheet may stop working because the browser thinks that the friendly URL is the base URL of the resource. To solve this problem, you should use absolute references to such resources, such as
#{facesContext.externalContext.request.contextPath}/stylesheet/theme.css
It’s also possible to create friendly URLs using the inverse mechanism. If you use regu-lar HTML links, links created with <h:outputLink>, or Seam UI command compo-nents in your view, you can define outbound rules that convert link targets to friendly URLs before they are sent along with the response. Here’s an example of an outgoing rewrite rule that produces friendly course URLs for the course detail page:
Listing 3.5 URL rewrite configuration for friendly URLs
Matches URLs like
<outbound-rule>
<from>^(/.+)?/Course.seam\?courseId=(\d+)$</from>
<to>$1/course/view/$2</to>
</outbound-rule>
When defining outbound rules with strict matching (the leading caret), the context path (in this case /open18) must be captured in the <from> expression and passed on to the target URL. You also have to consider all the possible query parameters that may appear in the URL for the rule to be matched. A common parameter to watch out for is the conversation id parameter, thus requiring a more complex expression.
The UrlRewriteFilter is quite capable of slicing and dicing the URL in whatever cre-ative ways you can think of to write regular expressions. Not only is it useful for creating friendly URLs, but it can also help when you want to migrate a site to a new structure, serve custom resources based on the user agent (browser), or trim long or complicated URLs. You can see more examples of the UrlRewriteFilter in action in the example projects (under the examples directory) that come with the Seam distribution.
Now that you are well versed in Seam’s pages.xml configuration, it’s time to see how Seam ties this functionality into the JSF life cycle and what else it adds in the process.