Thursday, February 23, 2012

Building and Deploying the Seam 3 Booking example from Eclipse 3.7 to JBoss AS 7.1




This blog entry is about how to make the JBoss Seam 3 Booking Example to build and deploy under Eclipse 3.7 SR2 to JBoss AS 7.1 (under Windows).

There are two maven plugins which could help: The Maven Eclipse Plugin and the APT Plugin from Codehouse.

Maven 3.0.4 and Dependency versions

I got missing version information errors when upgraded to Maven 3.0.4, so I added the version information in every missing place (I have also removed the non JBoss 7 related profiles and testing related   goals) . The modified project pom somehow caused the Eclipse JDT-APT plugin to stop working, so I have added a new builder for running annotation processors (see later). This is how the modified pom looks like:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <version>3.0.1</version>
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.jboss.seam.examples.seam-booking</groupId>
    <artifactId>seam-booking</artifactId>
    <packaging>war</packaging>
    <name>Seam Booking Example</name>
    <description>The Seam booking example using the simplified Java EE 6 programming model and packaging structure (i.e., web archive)</description>
    <url>http://seamframework.org/Seam3</url>
    <properties>
        <jpamodelgen.version>1.1.1.Final</jpamodelgen.version>
    </properties>
    <dependencies>
        <!-- Annotation processor for generating typed loggers -->
        <dependency>
            <groupId>org.jboss.solder</groupId>
            <artifactId>solder-tooling</artifactId>
            <scope>compile</scope>
            <version>3.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.solder</groupId>
            <artifactId>solder-impl</artifactId>
            <scope>runtime</scope>
            <version>3.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>${jpamodelgen.version}</version>
            <scope>provided</scope>
            <!-- Excluded Hibernate-provided JPA API because it's provided by the Java EE 6 dependencies -->
            <exclusions>
                <exclusion>
                    <groupId>org.hibernate.javax.persistence</groupId>
                    <artifactId>hibernate-jpa-2.0-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.jboss.seam.faces</groupId>
            <artifactId>seam-faces-api</artifactId>
            <version>3.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.seam.faces</groupId>
            <artifactId>seam-faces</artifactId>
            <version>3.1.0.Final</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.seam.international</groupId>
            <artifactId>seam-international</artifactId>
            <version>3.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.seam.security</groupId>
            <artifactId>seam-security</artifactId>
            <scope>compile</scope>
            <version>3.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.seam.transaction</groupId>
            <artifactId>seam-transaction</artifactId>
            <scope>compile</scope>
            <version>3.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.picketlink.idm</groupId>
            <artifactId>picketlink-idm-core</artifactId>
            <version>1.3.0.GA</version>
        </dependency>
        <dependency>
            <groupId>com.ocpsoft</groupId>
            <artifactId>ocpsoft-pretty-time</artifactId>
            <version>1.0.7</version>
        </dependency>
        <dependency>
            <groupId>com.ocpsoft</groupId>
            <artifactId>prettyfaces-jsf2</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-digester3</artifactId>
            <version>3.2</version>
            <classifier>with-deps</classifier>
        </dependency>
        <!-- Bean Validation Implementation; provides portable constraints @NotEmpty, @Email and @Url -->
        <!-- Hibernate Validator is the only JSR-303 implementation at the moment, so we can assume it's provided -->
        <!-- TODO Move Hibernate Validator to app server specific sections -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.2.0.Final</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.1</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.spec</groupId>
            <artifactId>jboss-javaee-6.0</artifactId>
            <type>pom</type>
            <scope>provided</scope>
            <version>1.0.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.shrinkwrap.resolver</groupId>
            <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
            <scope>runtime</scope>
            <version>1.1.0-alpha-2</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>knowledge-api</artifactId>
            <version>5.4.0.Beta1</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.servicemix.bundles</groupId>
            <artifactId>org.apache.servicemix.bundles.drools</artifactId>
            <version>5.1.1_1</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <defaultGoal>package</defaultGoal>
        <finalName>seam-booking</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.0-alpha-4</version>
            </plugin>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalBuildcommands>
                        <!-- annoyingly creates a bin directory <buildCommand> <name>org.eclipse.wst.jsdt.core.javascriptValidator</name> </buildCommand> -->
                        <buildCommand>
                            <name>org.jboss.tools.common.verification.verifybuilder</name>
                        </buildCommand>
                    </additionalBuildcommands>
                    <additionalConfig>
                        <file>
                            <name>.settings/org.maven.ide.eclipse.prefs</name>
                            <content>eclipse.preferences.version=1
                                   fullBuildGoals=process-test-resources
                                   includeModules=false
                                   resolveWorkspaceProjects=true
                                   resourceFilterGoals=process-resources
                                   resources\:testResources
                                   skipCompilerPlugin=true
                                   version=1</content>
                        </file>
                    </additionalConfig>
                    <additionalProjectFacets>
                        <jst.jsf>2.0</jst.jsf>
                    </additionalProjectFacets>
                    <additionalProjectnatures>
                        <projectnature>org.eclipse.wst.jsdt.core.jsNature</projectnature>
                        <projectnature>org.jboss.tools.jsf.jsfnature</projectnature>
                    </additionalProjectnatures>
                    <workspace>d:\workspace</workspace>
                    <wtpdefaultserver>JBossAS</wtpdefaultserver>
                    <wtpversion>2.0</wtpversion>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <!-- The JBoss AS plugin deploys your war to a local JBoss AS container -->
            <!-- To use, set the JBOSS_HOME environment variable and run: mvn package jboss-as:deploy -->
            <plugin>
                <groupId>org.jboss.as.plugins</groupId>
                <artifactId>jboss-as-maven-plugin</artifactId>
                <version>7.1.0.Final</version>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <profile>
            <id>distribution</id>
            <activation>
                <property>
                    <name>release</name>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-assembly-plugin</artifactId>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>jbossas7</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property>
                    <name>arquillian</name>
                    <value>jbossas-managed-7</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-war-plugin</artifactId>
                        <version>2.0</version>
                        <configuration>
                            <webResources>
                                <resource>
                                    <directory>src/main/resources-jbossas7</directory>
                                </resource>
                            </webResources>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>jbossas-remote-7</id>
            <activation>
                <property>
                    <name>arquillian</name>
                    <value>jbossas-remote-7</value>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>org.jboss.seam.test</groupId>
                    <artifactId>jbossas-remote-7</artifactId>
                    <type>pom</type>
                    <scope>test</scope>
                </dependency>
            </dependencies>
        </profile>
        <profile>
            <id>jbossas-managed-7</id>
            <activation>
                <property>
                    <name>arquillian</name>
                    <value>jbossas-managed-7</value>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>org.jboss.seam.test</groupId>
                    <artifactId>jbossas-managed-7</artifactId>
                    <type>pom</type>
                    <scope>test</scope>
                    <version>LATEST</version>
                </dependency>
            </dependencies>
        </profile>
    </profiles>
    <scm>
        <connection>scm:git:git://github.com/seam/examples.git</connection>
        <developerConnection>scm:git:git@github.com:seam/examples.git</developerConnection>
        <url>http://github.com/seam</url>
    </scm>
</project>

Maven Eclipse Plugin

This plugin configures the Eclipse Workspace and creates/modifies the configuration files of Eclipse, so the maven project can be easily imported into Eclipse. I assume, you have imported the Seam Booking example project into your default eclipse workspace.

First, place the following plugin declaration into the <build> section of the pom file (as you have seen under the "Maven 3.0.4 and Dependency versions" section):

<plugin>
    <artifactId>maven-eclipse-plugin</artifactId>
    <version>2.9</version>
    <configuration>
        <additionalBuildcommands>
            <buildCommand>
                <name>org.jboss.tools.common.verification.verifybuilder</name>
            </buildCommand>
        </additionalBuildcommands>
        <additionalConfig>
            <file>
                <name>.settings/org.maven.ide.eclipse.prefs</name>
                <content>eclipse.preferences.version=1
                                   fullBuildGoals=process-test-resources
                                   includeModules=false
                                   resolveWorkspaceProjects=true
                                   resourceFilterGoals=process-resources
                                   resources\:testResources
                                   skipCompilerPlugin=true
                                   version=1</content>
            </file>
        </additionalConfig>
        <additionalProjectFacets>
            <jst.jsf>2.0</jst.jsf>
        </additionalProjectFacets>
        <additionalProjectnatures>
            <projectnature>org.eclipse.wst.jsdt.core.jsNature</projectnature>
            <projectnature>org.jboss.tools.jsf.jsfnature</projectnature>
        </additionalProjectnatures>
        <workspace>d:\workspace</workspace>
        <wtpdefaultserver>JBossAS</wtpdefaultserver>
        <wtpversion>2.0</wtpversion>
        <downloadSources>true</downloadSources>
        <downloadJavadocs>true</downloadJavadocs>
    </configuration>
</plugin>

Now, you can execute the plugin in the seam-booking directory:
mvn eclipse:eclipse

Now the eclipse project should be refreshed, to reflect the changes. If you try to build and deploy the application, it will fail, because the src/test/java directory is configured as source and the content will be included in the resulting war. I simply removed the src/test/java and src/test/resource folders from Project Properties/Java Build Path/Source.

Running the Annotation Processor in Seam Solder
You have several options to run the annotation processor to generate the typed logging class org\jboss\seam\examples\booking\logBookingLog_$logger.java. You can make Eclipse to run it or you can use ant for this purpose. I have used the APT Maven Plugin to configure the .factorypath file to enable running the annotation processor in Seam Solder Tooling, but later after adding the latest versions to the project pom's dependencies, the Eclipse APT was stopped working (no error messages, no results) so I changed to running the annotation processors using javac and ant (see later).  


APT Maven Plugin

This plugin can configure the Eclipse Annotation Processing Tool to be able to run the annotation processor in you maven project.

First, place the following plugin declaration into the build section of the pom file:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.0-alpha-4</version>
</plugin>


Now, you can execute the plugin in the seam-booking directory:
mvn apt:eclipse

The plugin will change the content of the ".factorypath" file in the project root directory, adding classpath entries to it. Eclipse's APT plugin will search trough this classpath and if an annotation processor factory is found, it tries to execute.

The first thing you should change is the name of the generated source directory. Go to Project properties/Java Compiler/Annotation Processing tab/Generated Source directory text box. Here enter a valid file name (e.g.: ".apt_generated", which is the default by Eclipse).

To see the list of files in the annotation processor classpath, go to the Project Properties/Java Compiler/Annotation Processor/Factory Path tab. The annotation processor for Seam 3 Booking example is found in "M2_REPO/org/jboss/solder/solder-tooling/3.1.0.Final/solder-tooling-3.1.0.Final.jar", where M2_REPO is the eclipse variable for the local maven repository. The Java 6 JDK tools.jar was missing from this factory path, so I manually added as: "D:\bin\jdk1.6.0_31\lib\tools.jar".

If you have installed JBoss 7.1 and configured in Eclipse, you should able to make the war file and deploy it to the application server (I assume you have installed the latest JBoss Tools eclipse plugin).

Using ant to run the annotation processors in your project

The JDT-APT functionality of Eclipse sometimes just doesn't work (see the previous chapter), so my alternative is to use ant and javac compiler to do it. First, create a build file for ant, and save it as build.xml in your project root:

<?xml version="1.0" encoding="UTF-8"?>
<project name="seam-booking-annotation-processing" basedir="." default="apt">
    <property name="src" value="./src/main/java" />

    <target name="apt">

        <javac compiler="javac1.6" srcdir="${src}">
            <include name="**/*.java" />
            <classpath path="${classpath}"/>
            <compilerarg line="-s ${src_gen}" />
            <compilerarg value="-verbose" />
            <compilerarg value="-proc:only" />

        </javac>
    </target>

</project>

Next, you should add a custom builder to your project: Project/Properties/Builders tab/New button:

Use "Ant Builder", then the "OK" button. The following dialog will appear. I have added the name "APT" to this custom builder. The Buildfile name is "build.xml" in the project root (which is "seam-booking" in the workspace)

The interesting part is the arguments for the ant task. The "classpath" parameter is passed to the ant build file containing the classpath, and the "src_gen" parameter contains the full path of the directory where javac will place the generated java code. The directory should be created manually, since javac doesn't create it, if missing.

-Dclasspath=${project_classpath:seam-booking}
-Dsrc_gen=${project_loc:seam-booking}/src-gen

Your builder list for the project should look like this:


Now you should add src-gen to the source paths of your project and do a build (Ctrl-B) to test if the build file is working. If the ant task is executed, it logs to the Console. I have used ant 1.7.1 and Java 1.6 u31.


Troubleshooting

When the application is not deploying, usually the src/test directories are included in the source directories.

When you click on a hotel name, and that results in an exception, this is probably caused by the missing typed loggers which should have been generated during the build process. In this case you should check the error log on why the annotation processor (Solder Tooling) haven't been run properly.

Monday, April 11, 2011

Dynamic Menus in RichFaces

RichFaces has no built-in functionality for creating dynamic menus, the reason for this could be that a lot of different use case scenarios require different type of dynamic menus. This example presents the most simple case: The menu items are dynamically populated from a list, and an action method is called with the id of the menu item, when the user selects the menu item.

The data of a menu item is represented by the MenuItem class:

public class MenuItem {

 /**
  * Id of the menu item
  */
 private int id;

 /**
  * Label for menu item
  */
 private String label;

 /**
  * Constructor
  * 
  * @param label The label fo the menu item
  * @param id the id of the menu item
  */
 public MenuItem(String label, int id) {
  super();
  this.label = label;
  this.id = id;
 }

 /**
  * @return the id
  */
 public int getId() {
  return this.id;
 }

 /**
  * @return the label
  */
 public String getLabel() {
  return this.label;
 }

 /**
  * @param id
  *            the id to set
  */
 public void setId(int id) {
  this.id = id;
 }

 /**
  * @param label
  *            the label to set
  */
 public void setLabel(String label) {
  this.label = label;
 }

 /**
  * @see java.lang.Object#toString()
  */
 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("MenuItem [id=");
  builder.append(this.id);
  builder.append(", label=");
  builder.append(this.label);
  builder.append("]");
  return builder.toString();
 }

}


The dynamic menu is supported by the DynamicMenu class. It provides the list of menu items and an action method:

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.jboss.seam.annotations.Name;

@Name("dynMenu")
public class DynamicMenu {
 
 private Logger log = Logger.getLogger(DynamicMenu.class.getName());

 /**
  * Example action method
  * 
  * @param id the menu item id
  */
 public void action(int id) {
  log.info("Action called with menu item id: " + id);
 }

 /**
  * Returns the list of menu items.
  * 
  * @return the list of menu items
  */
 public List getMenuItems() {

  List menuItems = new ArrayList();

  menuItems.add(new MenuItem("Menu Item #1", 1));
  menuItems.add(new MenuItem("Menu Item #2", 2));
  menuItems.add(new MenuItem("Menu Item #3", 3));

  return menuItems;
 }
}


The following code snippet contains the dynamic menu xhtml example. The key in the dynamic menu items is the <c:forEach> iterator. The namespace declaration is very important, it should be xmlns:c="http://java.sun.com/jstl/core". If you use xmlns:c="http://java.sun.com/jsp/jstl/core" namespace, the iterator won't work!

<h:form xmlns:h="http://java.sun.com/jsf/html">

...

   <rich:dropDownMenu
    value="Dynamic Menu Item Example"
    style="text-decoration: none;"
   >


    <c:forEach xmlns:c="http://java.sun.com/jstl/core"
     var="item"
     items="#{dynMenu.getMenuItems()}"
    >

     <rich:menuItem
      id="menuItem#{item.id}"
      submitMode="ajax"
      value="#{item.label}"
      action="#{dynMenu.action(item.id)}"
     >

     </rich:menuItem>
    </c:forEach>



   </rich:dropDownMenu>

...

</h:form>

Thursday, April 7, 2011

Speeding up a Seam Page in an Expensive Way

The Task

Given a Seam 2.2 application with an overview page displaying progress of students aiming to obtain a given certificate (I have changed the original purpose of the application in this article for discretion). The problem: the page was excessively slow. I was asked to cut the display time from 30 seconds to below 5 seconds.
The page looked like in the following: On the left side there are the "Trainees", on the header there are the "Exams", the other table cells contains the latest dates when a student has taken the given exam and the color shows the status. (The given certificate are previously selected, in this case the "Java Programmer" Certificate).

Declarations, Initialization and Scoping Flow Control API Contents Concurrency OO Concepts Collections / Generics Fundamentals
Alger, Brian 1/3/2011 2/5/2011 3/7/2011 n/a 5/7/2011 6/11/2011 7/19/2011
Algieri, Christopher 1/3/2011 2/5/2011 3/7/2011 4/14/2011 5/7/2011 n/a 7/19/2011
Barnett, Jason 1/3/2011 2/5/2011 3/7/2011 4/14/2011 5/7/2011 6/11/2011 7/19/2011
Dobrin, Adam 1/3/2011 2/5/2011 3/7/2011 4/14/2011 5/7/2011 6/11/2011 7/19/2011
... ... ... ... ... ... ... ...
Fogelson, Benjamin 1/3/2011 2/5/2011 3/7/2011 n/a 5/7/2011 6/11/2011 7/19/2011

Legend:
Finished Failed Waiting



Is There a Cheap Solution?

I wanted to solve the problem by applying some kind of "silver bullet", so I entered "speed up seam application" into google. The most interesting article: Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude promised a 100 times ("two orders of magnitude") speed increment by applying the following steps:

  • Remove the unnecessary page action calls
  • Simplify the EL expressions in conditional rendering
  • Avoid calling intercepted methods
  • Avoid using the EL variable resolver, use the corresponding context directly to get the variable's value
  • Reduce the size of the resulting HTML code by reducing the length of IDs, paths; link JavaScript and CSS instead of embedding
  • Disable facelet refresh

Most of the tips are about changing the JSF page or the "page.xml", I have tried my best but with no luck...

Caching

I have tried to use EHCache using this (and some other) guides: Seam and EhCache – A Short Primer. After installing EHCache I have to admit the performance was still low, although after refreshing the page, it was displayed a bit faster.

Investigation

At this point it was clear that no cheap tricks will work, so I had to find out what takes 30 seconds to display the page. Adding ad-hoc instrumentation code into the page action method and into the page xhtml code and by measuring the time using Google Chrome Developer Tools, I found out that

  • 33% of the time spent on database queries and preparing the data in the action method
  • 33% of the time spent on processing the xhtml page and produce the output
  • 33% was spent on displaying the page by the client (waiting for the page data)


First Bottleneck: Data model


By looking at the Hibernate entities and queries, it was astonishing to see how complicated is to collect the data to display the overview. Let us see the data model which is important for the display.


Fig 1. The actual data model
To get a given Certificate the Trainees should take Exams (Certificates and Exams are linked together, but not shown in this model). If a change happens in the Trainee's status (e.g.: finishes an exam), a new Training Event is created linked to a Status. To display the table of all Trainees who are eager to get a given Certificate, the latest Training Events for a given Trainee-Certificate-Exam triplet should be queried. To select the latest training events a sub query is used:


SELECT 
  te FROM TrainingEvent te 
WHERE 
  te.certificate = :certificate AND 
  te.timestamp 
IN 
(SELECT 
    MAX(teLatest.timestamp) 
    FROM 
      TrainingEvent teLatest
    WHERE 
      teLatest.certificate = :certificate 
   GROUP BY 
      teLatest.exam)


Second bottleneck: RichFaces


The number of Exams for a given Certificate is variable and assigned in different parts of the application. To enable displaying dynamic columns for a table, RichFaces has the rich:columns JSF component. In our application this looked like this:

<rich:columns index="ind" style="background-color: #{certificateBean.getLatestTraining(trainee, exam) != null ?certificateBean.getLatestTraining(trainee, exam).status.backgroundColor :'#e0e0ff'};" styleclass="tightTable" value="#{certificateBean.getExams}" var="exam">
    <f:facet name="header">
        <h:outputtext value="#{exam.name}">
    </h:outputtext></f:facet>
    <ui:include src=" /trainingEventPanel.xhtml">
        <ui:param name="event" value="#{certificateBean.getLatestTraining(trainee, exam)}">
    </ui:param></ui:include>
</rich:columns>

The problem with this is the extensive number of calls to
certificateBean.getLatestTraining(trainee, exam)
 
I have eliminated the "heavy" calls to this method to check if rich:columns can be used effectively, but my "experiments" showed using rich:columns inevitably goes hand in hand with a lot of EL expression evaluation accountable for poor performance.

Terminal Solution


I have decided to basically rewrite the whole code, but leave the Hibernate entities alone — changing them would have meant changing the whole application. On the database/entities level I left unchanged everything, but my new logic was to implement a specific cache for the data needed to display the page and on the page I discarded the RichFaces components (rich:dataTable, rich:columns), instead I tried to emulate them with plain and simple HTML <table> tag.

Seam components, displayed in the following figure, are used for caching. The "AppCache" is an Application scope component, which is initialized at the start of the application. It holds the TraineeEventData component which contains the data to be displayed (by Certificate). The TraineeEventDataGenerator component is used to initially "generate" the data or refresh after a change. The TraineeEventDataLoader component is used to query the data from the database, for greater efficiency it uses scalar queries (instead of entity queries) to generate much less network traffic.

Fig. 2: The Caching Components

On the display side, instead of rich:dataTable, simple HTML table is used for display. The rows and columns are displayed by using <ui:repeat> components. The TraineeEventData has a method which can provide the information for the table cells as a linear list, so the HTML generation can be very quick.

<table id="traineeEventTable" class="te_table">
    <!-- header rows -->
    <tr class="te_header_row">
        <ui:repeat value="#{traineeEventTable.getHeaderCells()}" var="cell">
            <td class="#{cell.getStyleClass()}" width="#{cell.getWidthRef()}">
                <s:fragment rendered="#{cell.renderColumnHeader()}">#{cell.getContent()}</s:fragment>
            </td>
        </ui:repeat>
    </tr>
    <!-- normal rows -->
    <ui:repeat value="#{traineeEventTable.getRows()}" var="row" offset="1">
        <tr class="te_normal_row">
            <ui:repeat var="cell" value="#{row.cells}">
                <td width="#{cell.columnIndex==0?'200':''}" bgcolor="#{cell.getBackgroundColorRef()}" class="#{cell.getStyleClass()}">
                    <!-- Trainee names -->
                    <s:fragment rendered="#{cell.renderTraineeHeader()">
                        <table class="te_trainee_table">
                            <tr>
                                <td>
                                    <h:outputText rendered="#{cell.renderTraineeHeader()}" value="#{cell.trainee.traineeDisplayName}" id="traineecell" title="#{cell.hint}"/>
                                </td>
                            </tr>
                        </table>
                    </s:fragment>
                    <!-- not available content -->
                    <s:fragment rendered="#{cell.isNotAvailable()}">n/a</s:fragment>
                    <!-- normal content -->
                    <s:fragment rendered="#{cell.isContentRendered()}">
                        <h:outputText style="color: #{cell.textColorRef};" id="cellcontent" value="#{cell.content}" title="#{cell.hint}"/>
                    </s:fragment>
                </td>
            </ui:repeat>
        </tr>
    </ui:repeat>
</table>


The s:fragment component is used to control generation of entire blocks.

What happens on an update which affects the trainee status table? Originally there was a periodic process using Quartz scheduler to update the table, but it was inadequate for the users. Then I was thinking about making a Hibernate event handler which observes changes in the appropriate entities and updates the data when necessary. The final solution is even simpler, a small utility method is called on those locations when the data is changed:

package com.trainee.util;

import com.trainee.TraineeEventDataGenerator;

import org.jboss.seam.Component;

public class AppCacheUtil {
 /**
  * Calls the Application Cache Updater to update the trainee status table
  * data
  */
 public static void updateAppCache() {

  TraineeEventDataGenerator generator = (TraineeEventDataGenerator) Component
    .getInstance("traineeEventDataGenerator");
  generator.generate();

 }
}


Moral

What lesson we can draw from this case? The first is that the ER model should be always efficient even if the application we are working on is just a prototype (as it were in my case). With a better data model the page display time could have been initially much less. On the other hand the current technology (e.g.: RichFaces) using out-of-the-box is not always able to provide the desired performance and in these cases a custom solution should be made.