JBoss Community Archive (Read Only)

RHQ 4.9

Design-JSFExtensions

Module Layout, Deployment, & Installation

The RHQ user interface is broken down into three primary modules: installer-war, portal-war, and webdav-war. All of these can be found under "modules/enterprise/gui", as the following directory snapshot illustrates. This document, however, will focus mostly on portal-war.

images/author/download/attachments/73139477/rhq-ui-dev-modules.png

The left-hand side of the image above shows the RHQ source tree module hierarchy. The right-hand side shows the file structure of the product in its deployed form. Using the colored box highlights, you can see where each module from the source tree can be found in the product installation.

Explanation of ".rej"

Since JBossAS uses a http://www.redhat.com/docs/manuals/jboss/jboss-eap-4.2/doc/Server_Configuration_Guide/The_JBoss_JMX_Microkernel-JBoss_Deployer_Architecture.html suffix deployer, any file that ends in ".ear" or ".war" will be interpreted as an Enterprise Application or Web Archive, respectively, and the server will attempt to deploy it. So, in order to prevent the server from deploying the rhq.ear before other dependencies have been laid down, RHQ uses a ".rej" suffix by default. Below is an illustration showing what the directory looks like before (left-hand side) and after (right-hand side) the installer is run.

images/author/download/attachments/73139477/rhq-ui-dev-installer.png

The diagram shows us that the rhq-installer.war does not have the ".rej" extension out-of-box. After the installer is run, it will get the ".rej" suffix added to it. This is to prevent users from accidentally rerunning the installer. In fact, postinstaller.war has logic redirect the user to the main start page for the "rhq-portal.war" if the user ever tries to access some URL that the installer uses.

Conversely, the rhq-postinstaller.war and rhq.ear are both suffixed with ".rej" out-of-the-box, and as a result of running through the installation that suffix is removed...which will deploy them.

JSF Container Configuration

The faces-config.xml file found in WEB-INF directory is the standard place to put all JSF-related configuration elements including:

  • Components

  • Managed Beans

  • Navigation Rules

  • View Handlers

  • Phase Listeners

  • Validators

  • Converters

However, instead of having one huge XML file (which would have been several thousands of lines long) RHQ breaks this information into separate files.

   <!--  componentType      := configuration, error, inventory, layout, navigation,
                               pagination, sorting, table, time, upload, metric

        resourceBeanType   := alert, content, autodiscovery, cluster, configuration
                              global, inventory, admin, measurement, operation, test
        autoGroupBeanType  := inventory, measurement
        commonBeanType     := availability, event, measurement, navigation
        definitionBeanType := group-definition
        groupBeanType      := alert, configuration, inventory, operation, measurement

        resourceNavType    := alert, cluster, content, events, auto-discovery,
                              configuration, inventory, admin, operation, measurement,
                              summary, test
        definitionNavType  := group-definition
        groupNavType       := alert, configuration, events, inventory, operations,
                              measurement
        autoGroupNavType   := events, measurement
        subsystemNavType   := tab-container

        converterType      := common, content, inventory
     -->
   <context-param>
      <param-name>javax.faces.CONFIG_FILES</param-name>
      <param-value>
         /WEB-INF/jsf-components/{componentType}-components.xml,

         /WEB-INF/jsf-managed-beans/{resourceBeanType}-beans.xml,
         /WEB-INF/jsf-managed-beans-auto-group/{autoGroupBeanType}-beans.xml,
         /WEB-INF/jsf-managed-beans-common/{commonBeanType}-beans.xml,
         /WEB-INF/jsf-managed-beans-definition/{definitionBeanType}-beans.xml,
         /WEB-INF/jsf-managed-beans-resource-group/{groupBeanType}-beans.xml,
         /WEB-INF/jsf-managed-beans-subsystem/{subsystem}-beans.xml,

         /WEB-INF/jsf-navigation/{resourceNavType}-navigation.xml,
         /WEB-INF/jsf-navigation-auto-group/{autoGroupNavType}-navigation.xml,
         /WEB-INF/jsf-navigation-definition/{definitionNavType}-navigation.xml,
         /WEB-INF/jsf-navigation-resource-group/{groupNavType}-navigation.xml,         
         /WEB-INF/jsf-navigation-subsystem/{subsystemNavType}-navigation.xml,

         /WEB-INF/jsf-converters/{converterType}-converters.xml
      </param-value>
   </context-param>

As you can see, there are several subdirectories for the various configuration files:

  • WEB-INF/jsf-components

  • WEB-INF/jsf-converters

  • WEB-INF/jsf-managed-beans*

  • WEB-INF/jsf-navigation*

Since there are only a handful of components and converters, all of those elements were placed into a single folder by type. However, because there are literally dozens (if not a few hundred) managed beans and navigation rules, these elements have been broken down into further subdirectories. These subdirectory suffix names are described below:

'''Subdirectory Name Suffix'''

'''Implied URL Mapping'''

<empty>

/rhq/resource/*.xhtml

-auto-group

/rhq/autogroup/*.xhtml

-common

/rhq/common/*.xhtml

-definition

/rhq/definition/*.xhtml

-resource-group

/rhq/group/*.xhtml

-subsystem

/rhq/subsystem/*.xhtml

Using this knowledge, you can very easily wire the Java code together with the definitions of the managed beans and navigation rules that describe it.

Let's say I'm looking at the inventory > overview sub-tab for a resource:

images/author/download/attachments/73139477/tab-inventory-overview.png

Then, without even looking at the URL, I know...

  • the facelets are in /rhq/resource/inventory/*.xhtml

  • the managed beans are in /WEB-INF/jsf-managed-beans/inventory-beans.xml

  • the navigation rules are in /WEB-INF/jsf-navigation/inventory-navigation.xml

If I were instead looking at the operation > schedules sub-tab for a resource group:

images/author/download/attachments/73139477/tab-operation-schedules.png

Then, again, without even looking at the URL, I know...

  • the facelets are in /rhq/group/operation/*.xhtml

  • the managed beans are in /WEB-INF/jsf-managed-beans-resource-group/inventory-beans.xml

  • the navigation rules are in /WEB-INF/jsf-navigation-resource-group/inventory-navigation.xml

JSF & RichFaces Extensions

URL Parameter Passing

The JSF 1.x framework only supports POST. Support for GET was never in its design. As a result, look what happens to the following workflow:

  • User clicks a link to navigate to some resource group-specific page

  • This means that URL will have the 'groupId' parameter in it

  • User edits some form data

  • User clicks OK (assume success) and is redirected to the success page

As a result of the redirection, the URL for the success page will no longer contain the 'groupId'. This has two major issues:

1. It prevents the user from using browser bookmarks (because the target page requires the 'groupId' parameter to exist for bookmark-ability)
2. It quite possibly breaks other navigation on this page

Issue (1) could be bypassed if you built the concept of bookmarks into the web application itself, but this is non-trivial to do in practice and wouldn't be much more powerful than browser bookmarks (if at all).

Issue (2) is far more severe. For instance, the listAlertDefinition.xhtml page (the facelet that displays the list of alert definitions for some resource), needs to pass the id of the resource through to the workflow that creates a new alert definition. The fragment looks like this:

   <h:commandButton action="#{ListAlertDefinitionsUIBean.createNewAlertDefinition}"
                    value="NEW DEFINITION" ... >
      <f:param name="id" value="#{param.id}"/>
      <f:param name="mode" value="new"/>
   </h:commandButton>

Notice how "#{param.id}" is used to pass the data to this other workflow. This is why it's critical to be able to put the identifiers into the URL after a form post. If the contextual data in the URL were lost, the facelet would either render elements with null data, or fail to render altogether.

Support for passing URL parameters across redirect boundaries was added to RHQ by enhancing the standard JSF lifecycle. Two elements were added to the faces-config.xml file:

<faces-config>
   <application>
      <view-handler>
         org.rhq.enterprise.gui.common.framework.FaceletRedirectionViewHandler
      </view-handler>
   </application>
   ...
   <lifecycle>
      <phase-listener>
         org.rhq.enterprise.gui.common.framework.FacesMessagePropogationPhaseListener
      </phase-listener>
   </lifecycle>
</faces-config>

FaceletRedirectionViewHandler

This class extends the standard FaceletViewHandler by injecting additional logic. In particular, it overrides the mechanism which generates the target URL as follows:

    /*
     * evaluate any EL variables that are contained in the view id.
     *   e.g. "/rhq/resource/inventory/view.xhtml?id=#{param.id}"
     */
    public String getActionURL(FacesContext facesContext, String viewId) {
        ValueExpression valueExpression =
           FacesExpressionUtility.createValueExpression(viewId, String.class);
        String actionURL = 
           FacesExpressionUtility.getValue(valueExpression, String.class);
        return actionURL;
    }

As the JavaDoc implies, this assumes that the standard JSF viewId passed to the handler has this augmented syntax. This allows you to use http://java.sun.com/javaee/5/docs/tutorial/doc/bnahq.html UEL (unified expression language) "#{token}" mechanism in any of your URLs in any of your *-navigation.xml files. For example, the /WEB-INF/jsf-navigation/configuration-navigation.xml file has the following fragment:

   <navigation-rule>
      <from-view-id>/rhq/resource/configuration/view.xhtml</from-view-id>
      <navigation-case>
         <from-action>#{ExistingResourceConfigurationViewUIBean.edit}</from-action>
         <to-view-id>/rhq/resource/configuration/edit.xhtml?id=#{param.id}</to-view-id>
         <redirect/>
      </navigation-case>      
   </navigation-rule>

When I first navigate to the configuration > current sub-tab for some resources, the URL will look like /rhq/resource/configuration/view.xhtml?id=<myResourceId>

Since the URL contains the id parameter, if I were to click on the edit button on this page (which is backed by ExistingResourceConfigurationViewUIBean.edit), the view handler above will evaluate "#{param.id}" and consequently redirect the user to the following page /rhq/resource/configuration.edit.xhtml?id=<myResourceId>. Notice how myResourceId was propagated through.

However, the view handler performs a full redirect (governed by the <redirect/> tag in the <navigation-case> element) in order to change the URL. This creates issues of its own because additional data might have been available in the context, which is now lost across the redirect boundary. In particular, the standard JSf mechanism for error handling - the FacesMessages object - is stored in that context. Thus, there would be no way to display a success (or error) message on the page that you redirect to. The next section, FacesMessagePropogationPhaseListener, presents the solution RHQ uses to bypass this.

FacesMessagePropogationPhaseListener

There are 6 phases in the http://java.sun.com/j2ee/1.4/docs/tutorial/doc/JSFIntro10.html JSF lifecycle:

1. Restore View
2. Apply Request Values
3. Process Validations
4. Update Model Values
5. Invoke Application
6. Render Response

The imaginary line between the "Invoke Application" and the "Render Response" phases essentially forms our redirect boundary. While the lifecycle is in one of the first 5 phases, it still processing on the current page. The "Invoke Application" phase is the execution of the form action itself, which must return a String identifying the viewId you which to navigate to. An example will help explain this boundary.

On the listAlertHistory.xhtml page, there is a button which deletes ALL of the alerts on that resource:

   <h:commandButton action="#{ListAlertHistoryUIBean.purgeAllAlerts}"
                    value="PURGE ALL" ... />

That backing bean looks as follows:

   public String purgeAllAlerts() {
      Subject subject = EnterpriseFacesContextUtility.getSubject();
      Resource resource = EnterpriseFacesContextUtility.getResource();

      try {
         AlertManagerLocal alertManager = LookupUtil.getAlertManager();
         int numDeleted = alertManager.deleteAlerts(subject, resource.getId());
         FacesContextUtility.addMessage(FacesMessage.SEVERITY_INFO, 
                                        "Deleted " + numDeleted + " alerts ...");
      } catch (Exception e) {
         FacesContextUtility.addMessage(FacesMessage.SEVERITY_ERROR, 
                                        "Failed to delete alerts ... ", e);
      }
      return "success";
   }

As with every other managed bean method in JSF that supplies the backing action to commandButtons and commandLinks, it returns an "outcome" string that is used to lookup the navigation rule that should fire. Since this method only returns a single value - "success" - it has one and only one page it will ever navigate to.

Using our knowledge about the layout of the various *-navigation.xml files, and since we know this button is found on the resource-level listAlertDefinition.xhtml page, it will map to the following navigation rule in /WEB-INF/jsf-navigation/alert-navigation.xhtml:

<!-- Alert History List -->
<navigation-rule>
   <from-view-id>/rhq/resource/alert/listAlertHistory.xhtml</from-view-id>
   <navigation-case>
      <!-- catch all navigation-case: if any actions return 'success', go here -->
      <from-outcome>success</from-outcome>
      <to-view-id>/rhq/resource/alert/listAlertHistory.xhtml?id=#{param.id}</to-view-id>
      <redirect/>
   </navigation-case>
</navigation-rule>

In other words, when the "PURGE ALL" button is clicked, it will:

  • purge the alerts

  • succeed or fail

  • write an appropriate error message into the FacesContext

  • the navigation rule that matches the "success" outcome will be invoked

  • our FaceletRedirectionViewHandler will dereference "#{param.id}"

  • the page flow will cross the redirection boundary

So, in order to propagate the data stored in the FacesContext, it needs to be:

1. pulled out just before the new page is rendered (just after the action method is invoked)
2. stored in some temporary location that exists across the redirect boundary
3. reinserted into the FacesContext after the new view is displayed (just before the new view is rendered / restored)

The class FacesMessagePropogationPhaseListener, which leverages the user's web session as the temporary storage location, will do just the trick:

public class FacesMessagePropogationPhaseListener implements PhaseListener {

   public PhaseId getPhaseId() {
      return PhaseId.ANY_PHASE;
   }

   public void afterPhase(PhaseEvent event) {
      if (event.getPhaseId() == PhaseId.INVOKE_APPLICATION) {
         // want to store the messages in the context after the app is done processing
         putGlobalFacesMessagesInSession();
      }
   }


   public void beforePhase(PhaseEvent event) {
      if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
         /* 
          * want to add the saved messages back to the context immediately after the
          * view is restored.  remove them from the session once we've added them
          * back, otherwise the messages will just keep building up across requests
          */
         List<FacesMessage> savedMessages = removeGlobalFacesMessagesFromSession();
         putGlobalFacesMessagesInFacesContext(savedMessages);
      }
   }
}

Summary

Thus, by leveraging the FacesMessagePropogationPhaseListener to close the gaps created by supporting URL parameter passing with FaceletRedirectionViewHandler, RHQ effectively bypasses the "everything is a POST" restriction of JSF and supports actions that can redirect to bookmarkable URLs while maintaining data across the redirect boundary.

Paging and Sorting

Many UI pages within RHQ need to display portions of very large sets of data. One way of doing this would be to add arguments at the business logic layer as follows:

   public List<Resource> findResources(Subject subject,
                                       String nameFilter,
                                       int startingRecord,
                                       int numberOfRecords,
                                       String sortBy);

The problem with this model is that if you ever wanted to change your application's pagination or sorting logic, you would have to update every single one of your methods that support paging across the entire business tier. Let's say that you wanted to expose an easier method for people to call who didn't want to paginate and just wanted all of the results back. You would either have to convert the 'int' types to 'Integer', or create a new overloaded method that only did the sorting:

   public List<Resource> findResources(Subject subject,
                                       String nameFilter,
                                       String sortBy);

Or, let's say you had an enhancement request to support ascending or descending order on your field. Your new method would look like:

   public List<Resource> findResources(Subject subject,
                                       String nameFilter,
                                       int startingRecord,
                                       int numberOfRecords,
                                       String sortBy,
                                       boolean isAscending);

In all of these cases, you're either:

  • Adding more methods, which bloats your interface)

  • Changing the invariants on existing methods by making certain parameters null-able, or

  • Modifying the signature of the method outright, which will require changing all callers to that method

Again, if you had to do this for every method across your entire business tier, it could be quite tedious. So RHQ took a slightly different route to support paging and sorting. Instead of using static parameters to control what data is retrieved, an abstraction was developed in the form of two objects called PageControl and PageList. As a result, all methods in RHQ that need to return a subset of data will look as follows:

   public PageList<Resource> findResources(Subject subject,
                                           ...
                                           PageControl pageControl);

The next sections describe these objects in detail as well as how to effectively leverage them.

PageControl

This object embodies the components needing for sorting and paging:

  • Wraps page number & page size

  • Provides reasonable defaults (the first page, showing 100 records)

  • Has accommodations for returning all results (i.e., ignore pagination)

  • Wraps sorting information

  • Has accommodations for default sorting rules

  • Can perform an ordered sort of up to 3 fields

  • Explicit specification of ASC and DESC property for each field

Note: in early versions of RHQ, you could only sort on a single field ascending or descending. However, because all the paging logic was completely contained in the PageControl object, it was very easy to enhance that class to support ordering on 3 fields independently. As a result, all business tier methods that were using it automatically inherited the new functionality.

PageList

Although returning a simple java.util.List would work, it would make rendering the user interface portion for those results more difficult. Any reasonably sophisticated UI will want to indicate to the user:

  • which page of data they are current viewing

  • providing paging controls for moving forward / backward through the result set

  • also give the user the ability to change the page size

  • display the total number of results found (even though only one page of data is being displayed)

  • enable sorting on individual columns in tabular displays

  • allow the user to flip the ASC / DESC order for any field

As a result, RHQ created the PageList object as an extension of Java's basic ArrayList, which compositionally includes the PageControl in it. Accordingly, the generic structure for the implementation of a paginated business method should be written as follows:

public PageList<Resource> findResources(Subject subject,
                                        ...
                                        PageControl pageControl) {
   List<Resource> results = // get results from Hibernate
   long count = // get total number of Objects of the given type
   return new PageList<Resources>(results, count, pageControl);
}

PersistenceUtility

In standard EJB3, the interaction with the EntityManager would roughly be as follows:

@NamedQueries( {
    @NamedQuery( name = "FindAllResources", 
                 query = "SELECT res FROM Resource res ORDER BY ..."),
    @NamedQuery( name = "FindAllResources_count", 
                 query = "SELECT COUNT(*) FROM Resource res")
} )

Query query = em.createNamedQuery("FindAllResources");
query.setFirstResult(pageNumber * pageSize);
query.setMaxResults(pageSize);
List<Resource> results = query.getResultList();

Query countQuery = em.createNamedQuery("FindAllResources_count");
long count = ((Number) countQuery.getSingleResult()).longValue();

Although this does handle the pagination, this completely ignores sorting. Any sorting logic would have to be hard-coded in your NamedQuery. Furthermore, it requires you to essentially duplicate all of your queries: once to return a single page of the result set, and once to return the cardinality of result set.

The PersistenceUtility class was written to simplify the programming model for developers writing business interfaces for RHQ. With it, the above code reduces to:

Query query = PersistenceUtility.createQueryWithOrderBy(entityManager,
                                                        "FindAllResources", 
                                                        pageControl);
List<Resource> results = query.getResultList();

Query countQuery = PersistenceUtility.createCountQuery(entityManager, 
                                                       "FindAllResources");
long count = ((Number) countQuery.getSingleResult()).longValue();

Notice how the PageControl instance is passed to the "createQueryWithOrderBy" method. It does several things for us:

  • talks to the EntityManager and get the underlying JPQL for that NamedQuery

  • uses regular expressions to transform that query into one dynamically populated with the ordering information contained within the PageControl object (you no longer have to statically embed this information in your NamedQuery)

  • still allows pagination rules by calling setFirstResult(int) and setMaxResults(int) on the query object, before returning it to the user

Notice that the user didn't have to write two different named queries. A single query, "FindAllResources", handled everything.

The "createCountQuery" will, in a similar fashion, talk to the EntityManager to get the underlying JPQL and use regular expressions to transform the existing select list into a query that performs "SELECT COUNT(*)..." auto-magically.

PagedDataTableUIBean / PagedListDataModel

These classes are integration points that function as the glue between the business tier (*ManagerBean SLSBs) and the web tier (*UIBean JSF Managed Beans). The facilities offered by them are:

  • The ability to link a specific instance of a PageControl object to a rich:dataTable defined in some facelet

  • Automatic persistence of the PageControl data on a user-by-user basis (pagination & sorting remembered if the user comes back to that view)

  • Implicit error handling of invalid PageControl data (such as trying to access a non-existent page), which consequently would reset (and re-persist) default pagination and sorting values for that user

  • Errors are captured and the underlying query that loads the table is automatically re-run without programmer intervention

  • Contains debug logging, which can be turned on to trace the performance of loading data into various tables

You never need to touch these on a day-to-day basis, just keep in mind all the features they are providing simply by writing your JSF managed beans as extensions to these classes. Let's look at an example, which loads the events for a resource:

public class EventHistoryUIBean extends PagedDataTableUIBean {

   @Override
   public DataModel getDataModel() {
      if (dataModel == null) {
         dataModel = new ListEventsHistoryDataModel(PageControlView.EventsHistoryList,
                                                    "EventHistoryUIBean");
      }
      return dataModel;
   }

   private class ListEventsHistoryDataModel extends PagedListDataModel<EventComposite> {

      public ListEventsHistoryDataModel(PageControlView view, String beanName) {
         super(view, beanName);
      }

      @Override
      public PageList<EventComposite> fetchPage(PageControl pc) {
         // get various context data such as search parameters and selected time range
         PageList<EventComposite> results = eventMgr.findEvents(getSubject(), 
                                                                getResource().getId(),
                                                                ...,
                                                                pc);
         return results;
      }
   }
}

The rendered table footer will look similar to:

images/author/download/attachments/73139477/datatable-pagination-scrollable.png

There are several parts to this:

  • Total - this is the 'count', which you used PersistenceUtility.createCountQuery to retrieve, and which you passed as the 2nd constructor argument when creating your PageList object

  • Items Per Page - this is bound to the pagesize property of the PageControl object that backs this data table; the user can freely change this and the table will re-render with more (or less) row results.

  • datascroller (right-most element) - this is bound to the pagenumber property of the PageControl object; it will only render if there are more elements than can fit on a single page; the user can freely change this and the table will re-render with the new page of data

PageControlView

Astute readers will notice that the PageControlView seems to play an integral part in the example above. Indeed, this object is the integration point that functions as the glue between this UIBean and the facelet which exposes that UIBean's functionality to the view. The PageControlView enum is how each table gets a semantic RHQ identity, which can then be used to remember the pagination and sorting information for this table on a user-by-user basis.

One thing to note about the PageControlView objects - the pagination and sorting are inextricably linked together. There are, however, valid cases where the data sets being dealt with are small, and you want to specifically disallow pagination but still enable / support sorting. This is easily done by creating your enumeration as follows:

{{
ResourceMeasurementScheduleList(ListResourceMeasurementScheduleUIBean.class, true)
}}

This then changes the style of the table footer to look like:

images/author/download/attachments/73139477/datatable-pagination-unlimited.png

Notice that the "Items Per Page" as well as the datascroller are missing. Since we're dealing with an unlimited PageControlView, the framework will only render the "Total" as a read-only label, and nothing else.

RichFaces DataTable

To expose the pagination functionality to your view (i.e., some facelet), we use a standard DataTable component from the RichFaces library, but we decorate it and annotate it in various ways. Let's take a look at /rhq/resource/configuration/history.xhtml to see how this works:

01   <ui:param name="configurationUpdateDataModel" 
02             value="#{ListConfigurationUpdateUIBean.dataModel}"/>
03   <rich:dataTable id="configurationUpdateDataTable"
04                   rows="#{PageControl.ConfigurationHistory.pageSize}"
05                   value="#{configurationUpdateDataModel}"
06                   var="item" >
07      <f:facet name="PageControlView">
08         <onc:paginationControl id="ConfigurationHistory"/>
09      </f:facet>
10      <rich:column>
11         <f:facet name="header">
12            <onc:sortableColumnHeader sort="cu.subjectName">
13               <h:outputText styleClass="headerText" value="User"/>
14            </onc:sortableColumnHeader>
15         </f:facet>
16         <h:outputText value="#{item.subjectName}"/>
17      </rich:column>
18      <f:facet name="footer">
19         <rich:columnGroup>
20            <rich:column colspan="...">
21               <!--  buttons -->
22               <ui:param name="paginationDataTableName"
23                         value="configurationUpdateDataTable"/>
24               <ui:param name="paginationDataModel" 
25                         value="#{configurationUpdateDataModel}"/>
26               <ui:param name="paginationPageControl" 
27                         value="#{PageControl.ConfigurationHistory}"/>
28               <ui:include src="/rhq/resource/include/pagination.xhtml"/>
29            </rich:column>
30         </rich:columnGroup>
31      </f:facet>
32
33   </rich:dataTable>

The various parts of this fragment are explained:

'''Lines'''

'''Description'''

01-02

Loading the dataModel from our UIBean, which in this case is going to be an instance of PagedListDataModel, and saving it into a parameter called 'configurationUpdateDataModel' to be referenced elsewhere in the view

04

Binding the number of rows to render / display for this table to the pageSize property of the PageControl instance associated with this table

05

Referencing the variable created in lines 01-02, which binds the PagedListDataModel loaded from our UIBean to this table

07-09

Giving this table its semantic RHQ identity, which is used to look up the appropriate PageControlView enum, and that is subsequently leveraged to retrieve the appropriate PageControl instance from the backing store

12-14

RHQ custom JSF component - it finds which DataTable it's a child of, looks up the PageControlView enum for that table, which is subsequently leveraged to retrieve the appropriate PageControl instance from the backing store that contains the user-specific sorting information for this table (see the SortableColumnHeader section below for more details)

22-28

The pagination.xhtml is a parameterized facelet that requires 3 variables to exist in context - 'paginationDataTableName', 'paginationDataModel', and 'paginationPageControl' - so that it knows how to generate the pagination-aware table footer

SortableColumnHeader / SortableColumnHeaderListener

Whereas the pagination elements in the previous sections worked to control how the footer of some DataTable rendered itself, this component controls how each individual column header will render. An example will help illustrate the functionality this component provides:

images/author/download/attachments/73139477/datatable-sortable-column-header.png

Looking at the headers for the operation history table, I can tell that it will:

  • list operations that have failed before operations that have succeed, because the data set is first being sorting on the 'Status' field in ascending order

  • list the most recent failed operations first, because the data set is secondarily being sorted on the 'Date Completed' field in descending order

You'll also notice that each of the column headers is clickable. When you click a column header, it will fire off an ActionEvent containing the string data you added to the "sort" attribute for the tag. That ActionEvent will be captured by an instance of the SortableColumnHeaderListener, which was wired up during the construction of each SortableColumnHeader component. The listener will then do the following:

1. find the DataTable that contains the SortableColumnHeader component that triggered the{{ActionEvent}}
a. assume that the DataTable is backed by a PagedListDataModel and get the PageControl object associated with that data model
2. using the PageControl object gotten in (1), it will change the field that was just clicked to the primary sort field, which has the follow side effects:
a. if that column had been anywhere in the sort list - whether as the primary, secondary, or tertiary sort field - it will have its ordering reversed (if it had been ASC it will "flip" to DESC, and vice versa)
b. the previous primary sort field, if any, becomes the secondary sort field
c. the previous secondary sort field, if any, becomes the tertiary sort field
d. the previous tertiary sort field, if any, will be dropped
3. invoke the navigation rule for the view that has "sort" as the outcome

This means that the corresponding /WEB-INF/jsf-navigation/operation-navigation.xml file would need to have the following rule:

   <!-- Resource Operation History -->
   <navigation-rule>
      <from-view-id>/rhq/resource/operation/resourceOperationHistory.xhtml</from-view-id>
      <navigation-case>
         <from-outcome>sort</from-outcome>
         <to-view-id>
            /rhq/resource/operation/resourceOperationHistory.xhtml?id=#\{param.id}
         </to-view-id>
         <redirect />
      </navigation-case>
   </navigation-rule>

Summary

So, let's recap what it takes to add paging and sorting to your tabular views:

  • Write your managed bean (*UIBean naming convention) as an extension of PagedDataTableUIBean

  • Override the getDataModel() method, and have it return an instance of PagedListDataModel<T> - the generic, T, should be specific to the data you want to display in your rich:dataTable

  • Add another element to the PageControlView enumeration, which wires the *UIBean you just created with a persistent, table-specific & user-specific instance of a PageControl object

  • Write your facelet using a rich:dataTable tag

  • Add an <f:facet> tag whose sole child is "onc:paginationControl"

  • The "id" attribute of "onc:paginationControl" should match the name of the PageControlView enumeration you just added

  • Decorate any, all, or none of the columns with SortableColumnHeader component wrappers

  • The "sort" attribute should be the precise JPQL fragment that you want to be dynamically appended to the query PersistenceUtility generates automatically for you

  • Add the navigation rule for sorting as shown in the previous section; make sure that you are passing any necessary context data as tokenized URL parameters (using the "#{}" syntax supported by the FaceletRedirectionViewHandler)

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-13 08:08:01 UTC, last content change 2013-09-18 19:41:39 UTC.