Seam PDF Rendering and JPA Native Queries

March 31, 2008

< Seam Portlet Bridge … | RichFaces Plugin Released >

One of the reasons why I’ve come to love Seam is that it really lives up to its appellation as a programming and integration model – because of its ability to let me use technologies which are complex in their own right without the need to learn the nitty gritty of them. One example is the use of facelets for email, something which if I had to do programmatically using the JavaMail API will be quite a hell. With Seam all I have to learn are new facelets tags related to email. I still have access to my Business Process, Application, Session,Conversation etc contexts and can use resources from these contexts just like a normal facelet which generates a web page. Another one is pdf rendering, and that is what we’ll be talking of today.

I’ll use the shopper application that I developed for my previous post on AJAX, DataTables and Seam. Before we go on though, let’s not forget to mention the need for the following jar files in your classpath as per Chapter 16 of the Seam reference manual.

  • itex.jar
  • jboss-seam-pdf.jar

Next add these declarations to your web.xml file

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

And these to your components.xml file, making sure the namespace is properly declared

<pdf:document-store use-extensions="true" />

Since we will be using charting from JFreeChart, we need to add these additional jars.

  •  jcommon.jar
  •  jfreechart.jar

I’ve decided to add two links that render pdf documents. One provides a pie chart of the products held in my database and their stock levels. Very simple use case, since all I have to do is to select the products I’m interested in into an ArrayList and display them.

public List<Product> getProductView(){
return entityManager.createQuery("Select p from Product p").getResultList();
}

Here is the section of the facelet that displays the contents of the resulting ArrayList.

    <p:image alignment="right" wrap="true" value="/jboss.jpg" />
    <p:font id="test" size="24"><p:paragraph spacingAfter="50">Chart</p:paragraph></p:font>
    <p:piechart title="Product Stock Levels" width="500" height="350" legend="true" is3D="true" plotForegroundAlpha=".25">
    <ui:repeat value="#{shopList.productView}" var="item">
          <p:data key="#{item.name}" value="#{item.stock}" />
    </ui:repeat>
    </p:piechart>

Products chart

Now that the easy one is out of the way, lets look at the second link. This is to render a table telling us the date a cart was created, the total cost and the number of products in that cart. Well, date and cost fields are already declared on the Cart entity we created and we could do cart.getCartItems().size and get the number of products in each cart. But I decided to use a less talked about feature of JPA – native queries. Native queries enable us write SQL statements to retrieve records in our database that may not have been captured, modelled or contained in an existing entity. These allow us to write very complex queries and apply aggregation functions and so on on data.

We begin by first examining our query

select cart.date_created as date,cart.cost as cost, count(cart_item.product_id) as products from cart left join cart_item on cart_item.cart_id=cart.id group by date

Hmm. We have three aliases/fields being fetched from the database. JPA requires the declaration of an @SqlResultSetMapping which assigns a name to a native query and the fields that will be fetched from that native query. I decided to declare mine on the Cart entity, though I could have declared it on any entity.

@SqlResultSetMappings({
    @SqlResultSetMapping(
    name = "productStock",
            columns = {
        @ColumnResult(name = "date"),
        @ColumnResult(name = "cost"),
        @ColumnResult(name = "products")
    })}

Having sorted out what I want to fetch and meeting JPA’s requirements on declaring a @SqlResultSetMapping, I now go on to fetch the data using the entityManager.

public List<Map> getCartView(){
        List<Object[]> results = entityManager.createNativeQuery(
"select cart.date_created as date,cart.cost as cost, count(cart_item.product_id) as products from cart left join cart_item on cart_item.cart_id=cart.id group by date","productStock").getResultList();
List data = new ArrayList<HashMap>();
        if (!results.isEmpty()) {
            for (Object[] result : results) {
                HashMap resultMap = new HashMap();
                resultMap.put("date", (Date)result[0]);
                resultMap.put("cost", result[1]);
                resultMap.put("products", result[2]);
                data.add(resultMap);
}
        }
return data;
}

There are lot of things to note here.

  1. We use entityManager.createNativeQuery(), not entityManager.createQuery().

  2. After stating the query, you must also give the name of the named query which you defined in the SqlResutSetMapping. Ours is “productStock”.

  3. Each row/record of data fetched from the database is assigned to an Object array. In this case “date” is in the 0th index, “cost” is in the 1st and so on. Since I’m fetching more than one, I use entityManager.getResultSet() and assign to a List.

Expression Language( EL) allows us to refer to data in a Map by its key, and that feels more natural to me on my facelet than referring to indexes of an array, so I rather iterate through my “results” List, creating a HashMap for each row/record from the database and using appropriate names to refer to the fetched data.

Now here’s the facelet that renders the table containing our records.

  <p:image alignment="right" wrap="true" value="/jboss.jpg" />
  <p:font id="test" size="24"><p:paragraph spacingAfter="50">Table</p:paragraph></p:font>
  <p:table  columns="3" headerRows="1">
  <p:cell ><p:paragraph alignment="center">Date</p:paragraph></p:cell>
  <p:cell><p:paragraph>Cost</p:paragraph></p:cell>
  <p:cell><p:paragraph>No. of products</p:paragraph></p:cell>
  <ui:repeat value="#{shopList.cartView}" var="data">
   <p:cell><p:paragraph><p:text  value="#{data.date}"><f:convertDateTime dateStyle="medium" type="both"/>
</p:text></p:paragraph></p:cell>
<p:cell><p:paragraph>#{data.cost}</p:paragraph></p:cell>
<p:cell><p:paragraph>#{data.products}</p:paragraph></p:cell>
</ui:repeat>
    </p:table>

Cart table

Explaining each of the individual tags on this facelet is out of the coverage area of this post and further details can be gleaned from the Seam Reference Manual. However using EL, I can just refer to the date field as #{data.date} and so on and we will all be none the wiser. Of particular interest is the use of the <p:text/> tag. It allows us to apply JSF formatting/conversion etc on data being rendered in a PDF, and here we needed to format the date using the f:convertDateTime. Another use of this is to determine if some data will be rendered using the standard “rendered” JSF component attribute.

The combination of bijection and contexts means that Seam can always find whatever I’m referencing in its appropriate context and display it for me, whether on a facelets web page, PDF, email or in an asynchronous process.


Added on 7th April 2008

Give the code a spin and see.


Seam Seminar at Kofi-Annan Centre, Accra

March 25, 2008

As part of efforts to create a greater awareness of enterprise java technologies and tools especially on JBoss Seam, I’ve been able to secure a seminar on the 24th April 2008 at the Kofi Annan Center (AITI-KACE), Accra Ghana. This is part of their Technology Transformation Seminar that they organise to talk about trends and technologies in IT.

The seminar is titled “Stateful, Secure Enterprise Applications with JBoss Seam” and as the title suggests will be centered on the use of the Seam framework in web-based enterprise application development.

Looking forward to meeting all interested “Seamers” there. Will make the slides available on this blog after the seminar. Thanks in advance to AITI-KACE for the opportunity.

My next post will be focusing on PDF report generation and subsequently a look at the new feature of natural conversations. Keep your eyes on the ball.


Seam JBoss Portlet Bridge Realeased

March 4, 2008

< AJAX Magic with DataTables | Seam PDF Rendering … >

I was glad last week when I heard that the JBoss Portlet Bridge has been released for deploying Seam projects in JBoss Portal.

I’ve been waiting for this for a while, since I find the idea of portals very interesting. I evaluated the SeamBookingPortlet example that comes with this release and its not bad. Looking forward to the opportunity to develop applications for a business in a portlet fashion, and will be blogging on my portlet learning experiences.

The last milestone I’m waiting for is the ability to deploy JBoss Portal on Tomcat, a feature which can only be available after the completion of the JBoss 5 project which includes the new JBoss Microcontainer. This feature will enable JBoss apps like JBoss Portal to be deployed on Tomcat.

JBoss, speed it up.


AJAX Magic With JSF DataTables in Seam 2.0

March 4, 2008

<Seam 2.0 on Tomcat … | Seam Portlet Bridge Released >
RichFaces 3.1.3.GA came with a some new controls, and the <rich:listShuttle/> was of some interest to me. However, for a particular use case I found it not sufficient for my needs, and had to roll out my own version of it with a little bit of Ajax to add.

Let me use this simple scenario to display what I needed to do. Assuming you were keeping a shopping cart. After users select an item they want to buy, you want them to specify the quantity of that item following which their total charge is calculated for them on the fly. This I thought of doing using two DataTables just like the <rich:listShuttle/> appears but on a sleeker (or is it cruder?) level.

I defined an interface with a simple set of methods which I felt would do the trick called InPlaceEditing.

public interface InPlaceEditing {

void editSelection();

void addSelection();

void removeSelection();

void cancelSelection();

}

Here is my”shopper” Seam component which implements the interface. Notice the use of 2 DataModels “products” and “selectedItems” and their corresponding DataModelSelections. This is to enable me select from one table to another.

@Name(“shopper”)

@Scope(ScopeType.CONVERSATION)

public class Shopper implements java.io.Serializable, InPlaceEditing {

private boolean edit;

@In

EntityManager entityManager;

@In

FacesMessages facesMessages;

 

@DataModel

List<Product> products;

 

@DataModelSelection(“products”)

private Product product;

 

@DataModel

private List<CartItem> selectedItems;

 

@DataModelSelection(“selectedItems”)

private CartItem selectedItem;

 

@Out(required = false)

private CartItem cartItem;

 

private BigDecimal total = new BigDecimal(0.0);

 

private static int count = 0;

 

@Begin(flushMode=FlushModeType.MANUAL)

public void beginShopping() {

products = entityManager.createQuery(“Select p from Product p”).getResultList();

selectedItems = new ArrayList<CartItem>();

}

The beginShopping() method starts a conversation and FlushMode is set to MANUAL. This means that all changes made to entities will be made persistent only if I call flush() on entityManager. Seam defaults to AUTO which means all changes to all managed entities are merged into the persistence context after every Seam action call. Trust me, for the purposes of this trick, you DON’T want automatic persistence context merging! Anyway this action populates the “products” DataModel with products already entered into the database. The result is the table below.

<a:outputPanel id=”productPanel”>

<rich:dataTable value=”#{products}” var=”product”>

<h:column>

<f:facet name=”header”>Product</f:facet>

<a:commandLink value=”#{product.name}” actionListener=”#{shopper.editSelection}” reRender=”editPanel”/>

</h:column>

<h:column>

<f:facet name=”header”>Price</f:facet>

#{product.price}

</h:column>

<h:column>

<f:facet name=”header”>Stock</f:facet>

#{product.stock}

</h:column>

</rich:dataTable>

</a:outputPanel>

Displayed editing panel

A look at the section of the shop.xhtml facelet shows an <a:commandLink/> – an Ajaxified version of the <h:commandButon/>. This supplies our “shopper” component with the selected product through the “product” data model selection through Ajax. Also take note of the “reRender” and <a:outputPanel/> tags, which specify which areas of our page should be Ajax refreshed after certain actions are called. In the editSelection() action a new CartItem (a join entity between a product and a shopping cart) containing the selected product is created and outjected.

public void editSelection() {

if (edit == false) {

cartItem = new CartItem();

cartItem.setId(count++);

cartItem.setProduct(product);

cartItem.setSelected(true);

return;

}

cartItem = selectedItem;

cartItem.setSelected(true);

edit = true;

}

I want to use the same action to represent selecting a new product (from “products”) and editing an already existing CartItem (from “selectedItems”). This is achieved with a private “edit” boolan field, which by default is false. Also since proper behaviour of DataModel and DataModelSelections depends on a properly implemented equals() method (i.e. instance uniqueness) I assigned artificial ids with the static “count” to each CartItem. Note that without setting flushMode to MANUAL, doing this will cause exceptions to be thrown down the lane somewhere. Finally setting the CartItem’s “selected” field to true enables the rendering of the panel in which the quantity of that product will be entered.

Displayed editing panel

 

DataTable with configured product

 

Once the quantity is specified, the addSelection() action is called. Because of the boolean “edit” we are able to determine if this is a new product selection or one from the existing list and respond appropriately, calculating the total cost of the users purchases so far and modifying the corresponding DataModels appropriately. In the case of an edit, the old computed cost of a product is deducted from the total, and re-computed and added to the total. To guarantee that the change in costs will reflect properly, the edited “cartItem” is removed and re-inserted in to the “selectedItems” DataModel.

It is also very easy to remove an already configured CartItem, again with the combination of <a:commandLink/> and the “selectedItem” DataModelSelection.

public void removeSelection() {

total.subtract(selectedItem.getCost());

selectedItems.remove(selectedItem);

}

Finally, the user may save their selection. This involves the creation of a new Cart object, which is persisted to get an ID and associated with the CartItems in the “selectedItems” DataModel. Note here that the fake Ids I generated for the CartItems will cause maximum trouble in the database, and so I set them to null to force the persistence context to generate proper Ids for them. Don’t forget the all important “entityManager.flush()” to make all changes permanent.

Completed Shopping

Finally I raise an event (“shopper.events.CartEdit”) which is being observed by the “shopList” Seam component’s list() action causing the “carts” DataModel to be refreshed with fresh data.

 

@Factory(“carts”)

@Observer(“shopper.events.CartEdited”)

public void list(){

Contexts.getSessionContext().set(“carts”, null);

carts = entityManager.createQuery(“Select c from Cart c”).getResultList();

}

Carts DataTable displaying products selected

The Job Is Done. This is use of Ajax and DataTables is quite simplistic, but I’ve used it in some really tight corners for some advance stuff. One scenario that I can see with this example is the requirement to remove a product from the list of products once selected and configured, or to put it back once it has been removed from the configured ones. Another will be how to edit an already persisted cart to remove or add products to that cart. I’ve just laid the foundation. With some tricks of your own, you should be able to achieve some serious magic.